sunpeak 0.20.36 → 0.20.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +4 -2
  2. package/bin/commands/inspect.mjs +291 -64
  3. package/bin/commands/test-init.mjs +6 -0
  4. package/bin/lib/eval/eval-runner.mjs +53 -1
  5. package/bin/lib/eval/eval-types.d.mts +27 -0
  6. package/bin/lib/eval/model-registry.mjs +2 -2
  7. package/dist/chatgpt/index.cjs +1 -1
  8. package/dist/chatgpt/index.js +1 -1
  9. package/dist/claude/index.cjs +1 -1
  10. package/dist/claude/index.js +1 -1
  11. package/dist/embed.css +1 -1
  12. package/dist/hooks/tool-data-store.d.ts +26 -0
  13. package/dist/hooks/use-tool-data.d.ts +3 -9
  14. package/dist/host/chatgpt/index.cjs +1 -1
  15. package/dist/host/chatgpt/index.js +1 -1
  16. package/dist/index.cjs +35 -21
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.js +35 -21
  19. package/dist/index.js.map +1 -1
  20. package/dist/inspector/index.cjs +1 -1
  21. package/dist/inspector/index.js +1 -1
  22. package/dist/inspector/inspector.d.ts +7 -0
  23. package/dist/inspector/use-inspector-state.d.ts +28 -0
  24. package/dist/{inspector-CiuT_2yA.js → inspector-BSha-CAW.js} +216 -75
  25. package/dist/inspector-BSha-CAW.js.map +1 -0
  26. package/dist/{inspector-BNWla95w.cjs → inspector-Chhc2GNO.cjs} +216 -75
  27. package/dist/inspector-Chhc2GNO.cjs.map +1 -0
  28. package/dist/lib/utils.d.ts +8 -7
  29. package/dist/mcp/index.cjs +5 -3
  30. package/dist/mcp/index.cjs.map +1 -1
  31. package/dist/mcp/index.js +5 -3
  32. package/dist/mcp/index.js.map +1 -1
  33. package/dist/mcp/server.d.ts +12 -1
  34. package/dist/style.css +22 -0
  35. package/dist/{use-app-Duar2Ipu.js → use-app-CtKy52kw.js} +62 -1
  36. package/dist/use-app-CtKy52kw.js.map +1 -0
  37. package/dist/{use-app-DUdnDLP5.cjs → use-app-xaiN0HAd.cjs} +62 -1
  38. package/dist/use-app-xaiN0HAd.cjs.map +1 -0
  39. package/package.json +8 -8
  40. package/template/dist/albums/albums.html +3 -3
  41. package/template/dist/albums/albums.json +1 -1
  42. package/template/dist/carousel/carousel.html +3 -3
  43. package/template/dist/carousel/carousel.json +1 -1
  44. package/template/dist/map/map.html +4 -4
  45. package/template/dist/map/map.json +1 -1
  46. package/template/dist/review/review.html +3 -3
  47. package/template/dist/review/review.json +1 -1
  48. package/template/node_modules/.bin/tsc +2 -2
  49. package/template/node_modules/.bin/tsserver +2 -2
  50. package/template/node_modules/.bin/vitest +2 -2
  51. package/template/node_modules/.vite/deps/_metadata.json +3 -3
  52. package/template/node_modules/.vite-mcp/deps/_metadata.json +20 -20
  53. package/template/node_modules/.vite-mcp/deps/vitest.js +7 -7
  54. package/template/node_modules/.vite-mcp/deps/vitest.js.map +1 -1
  55. package/template/package.json +1 -1
  56. package/template/tests/e2e/visual.spec.ts-snapshots/albums-fullscreen-chatgpt-darwin.png +0 -0
  57. package/template/tests/e2e/visual.spec.ts-snapshots/albums-fullscreen-chatgpt-linux.png +0 -0
  58. package/template/tests/e2e/visual.spec.ts-snapshots/albums-fullscreen-claude-darwin.png +0 -0
  59. package/template/tests/e2e/visual.spec.ts-snapshots/albums-fullscreen-claude-linux.png +0 -0
  60. package/template/tsconfig.json +2 -0
  61. package/dist/inspector-BNWla95w.cjs.map +0 -1
  62. package/dist/inspector-CiuT_2yA.js.map +0 -1
  63. package/dist/use-app-DUdnDLP5.cjs.map +0 -1
  64. package/dist/use-app-Duar2Ipu.js.map +0 -1
@@ -3480,20 +3480,50 @@ var twMerge = /* @__PURE__ */ createTailwindMerge(getDefaultConfig);
3480
3480
  function cn(...inputs) {
3481
3481
  return twMerge(clsx(inputs));
3482
3482
  }
3483
+ function currentPageIsLoopback() {
3484
+ if (typeof window === "undefined") return true;
3485
+ return isLocalNetworkHostname(window.location.hostname);
3486
+ }
3487
+ function normalizeHostname(hostname) {
3488
+ return hostname.toLowerCase().replace(/^\[(.*)\]$/, "$1");
3489
+ }
3490
+ function isLocalNetworkHostname(hostname) {
3491
+ const host = normalizeHostname(hostname);
3492
+ if (host === "localhost" || host === "0.0.0.0" || host === "::1") return true;
3493
+ if (host.startsWith("127.")) return true;
3494
+ const ipv4 = host.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
3495
+ if (ipv4) {
3496
+ const octets = ipv4.slice(1).map(Number);
3497
+ if (octets.some((octet) => octet < 0 || octet > 255)) return false;
3498
+ const [a, b] = octets;
3499
+ return a === 10 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 169 && b === 254;
3500
+ }
3501
+ return host.startsWith("fc") || host.startsWith("fd") || host.startsWith("fe80:");
3502
+ }
3483
3503
  /**
3484
3504
  * Returns true when `icon` is safe to use as the `src` of an `<img>` rendered
3485
- * inside the inspector chrome. Accepts http(s) URLs and `data:image/*` URIs
3486
- * for raster image types only. SVG data URIs are rejected because they can
3487
- * include `<script>`/event handlers that execute when the document parses
3488
- * the inline document (the `<img>` tag itself does not run scripts in modern
3489
- * browsers, but adjacent <object>/<embed>/<iframe> renders would). Anything
3490
- * else (emoji, plain text, javascript:, file:, etc.) falls through to the
3491
- * text-rendering path that already handles emoji icons.
3505
+ * inside the inspector chrome. Accepts https URLs, local http URLs while the
3506
+ * inspector itself is running locally, and `data:image/*` URIs for raster image
3507
+ * types only. SVG data URIs are rejected because they can include
3508
+ * `<script>`/event handlers that execute when the document parses the inline
3509
+ * document (the `<img>` tag itself does not run scripts in modern browsers,
3510
+ * but adjacent <object>/<embed>/<iframe> renders would). Anything else (emoji,
3511
+ * plain text, javascript:, file:, etc.) falls through to the text-rendering
3512
+ * path that already handles emoji icons.
3492
3513
  */
3493
3514
  function isAllowedIconUrl(icon) {
3494
- if (icon.startsWith("https://") || icon.startsWith("http://")) return true;
3495
- if (icon.startsWith("data:image/png") || icon.startsWith("data:image/jpeg") || icon.startsWith("data:image/gif") || icon.startsWith("data:image/webp")) return true;
3496
- return false;
3515
+ if (/^data:image\/(?:png|jpeg|gif|webp)(?:[;,]|$)/i.test(icon)) return true;
3516
+ let url;
3517
+ try {
3518
+ url = new URL(icon);
3519
+ } catch {
3520
+ return false;
3521
+ }
3522
+ if (url.protocol !== "http:" && url.protocol !== "https:") return false;
3523
+ const inspectorIsLocal = currentPageIsLoopback();
3524
+ if (isLocalNetworkHostname(url.hostname) && !inspectorIsLocal) return false;
3525
+ if (url.protocol === "http:" && !inspectorIsLocal) return false;
3526
+ return true;
3497
3527
  }
3498
3528
  //#endregion
3499
3529
  //#region src/inspector/hosts.ts
@@ -5991,16 +6021,12 @@ var SUNPEAK_INLINE_HELPER_SCRIPT = `
5991
6021
  //#region src/inspector/iframe-resource.tsx
5992
6022
  /**
5993
6023
  * Allowed origins for cross-origin script loading.
5994
- * - Local development: localhost, 127.0.0.1, file://
5995
6024
  * - Production: sunpeak-prod-app-storage.s3.us-east-2.amazonaws.com (serves user scripts)
6025
+ *
6026
+ * Loopback script URLs are handled separately in isAllowedUrl() so hosted
6027
+ * inspectors cannot be tricked into loading scripts from a visitor's machine.
5996
6028
  */
5997
- var ALLOWED_SCRIPT_ORIGINS = [
5998
- "https://sunpeak-prod-app-storage.s3.us-east-2.amazonaws.com",
5999
- "http://localhost",
6000
- "https://localhost",
6001
- "http://127.0.0.1",
6002
- "https://127.0.0.1"
6003
- ];
6029
+ var ALLOWED_SCRIPT_ORIGINS = ["https://sunpeak-prod-app-storage.s3.us-east-2.amazonaws.com"];
6004
6030
  /**
6005
6031
  * Escapes HTML special characters to prevent XSS via attribute injection.
6006
6032
  */
@@ -6538,6 +6564,9 @@ var VALID_SCREEN_WIDTHS = new Set([
6538
6564
  "tablet",
6539
6565
  "full"
6540
6566
  ]);
6567
+ function isSafeStoredString(value) {
6568
+ return typeof value === "string" && value.length <= 200 && !/[\u0000-\u001f\u007f]/.test(value);
6569
+ }
6541
6570
  function sanitizeStoredPrefs(raw) {
6542
6571
  if (!raw || typeof raw !== "object") return {};
6543
6572
  const obj = raw;
@@ -6545,6 +6574,8 @@ function sanitizeStoredPrefs(raw) {
6545
6574
  if (typeof obj.theme === "string" && VALID_THEMES.has(obj.theme)) prefs.theme = obj.theme;
6546
6575
  if (typeof obj.locale === "string") prefs.locale = obj.locale;
6547
6576
  if (typeof obj.displayMode === "string" && VALID_DISPLAY_MODES.has(obj.displayMode)) prefs.displayMode = obj.displayMode;
6577
+ if (typeof obj.containerHeight === "number" && Number.isFinite(obj.containerHeight)) prefs.containerHeight = obj.containerHeight;
6578
+ if (typeof obj.containerWidth === "number" && Number.isFinite(obj.containerWidth)) prefs.containerWidth = obj.containerWidth;
6548
6579
  if (typeof obj.containerMaxHeight === "number" && Number.isFinite(obj.containerMaxHeight)) prefs.containerMaxHeight = obj.containerMaxHeight;
6549
6580
  if (typeof obj.containerMaxWidth === "number" && Number.isFinite(obj.containerMaxWidth)) prefs.containerMaxWidth = obj.containerMaxWidth;
6550
6581
  if (obj.safeAreaInsets && typeof obj.safeAreaInsets === "object") {
@@ -6563,6 +6594,10 @@ function sanitizeStoredPrefs(raw) {
6563
6594
  if (typeof obj.screenWidth === "string" && VALID_SCREEN_WIDTHS.has(obj.screenWidth)) prefs.screenWidth = obj.screenWidth;
6564
6595
  if (typeof obj.sidebarWidth === "number" && Number.isFinite(obj.sidebarWidth)) prefs.sidebarWidth = Math.max(DEFAULT_SIDEBAR_WIDTH$1, Math.round(obj.sidebarWidth));
6565
6596
  if (typeof obj.rightSidebarWidth === "number" && Number.isFinite(obj.rightSidebarWidth)) prefs.rightSidebarWidth = Math.max(DEFAULT_SIDEBAR_WIDTH$1, Math.round(obj.rightSidebarWidth));
6597
+ if (typeof obj.timeZone === "string") prefs.timeZone = obj.timeZone;
6598
+ if (typeof obj.prodResources === "boolean") prefs.prodResources = obj.prodResources;
6599
+ if (isSafeStoredString(obj.modelProvider)) prefs.modelProvider = obj.modelProvider;
6600
+ if (isSafeStoredString(obj.modelId)) prefs.modelId = obj.modelId;
6566
6601
  return prefs;
6567
6602
  }
6568
6603
  function readStoredPrefs() {
@@ -6575,6 +6610,12 @@ function readStoredPrefs() {
6575
6610
  return {};
6576
6611
  }
6577
6612
  }
6613
+ function writeStoredPrefs(prefs) {
6614
+ if (typeof window === "undefined") return;
6615
+ try {
6616
+ localStorage.setItem(PREFS_KEY, JSON.stringify(prefs));
6617
+ } catch {}
6618
+ }
6578
6619
  function deriveContainerDimensions({ displayMode, containerHeight, containerWidth, containerMaxHeight, containerMaxWidth, measuredContentWidth, viewportHeight = 800, viewportWidth = 1280 }) {
6579
6620
  if (containerHeight != null || containerWidth != null || containerMaxHeight != null || containerMaxWidth != null) return {
6580
6621
  ...containerHeight != null ? { height: containerHeight } : {},
@@ -6623,8 +6664,8 @@ function useInspectorState({ simulations, defaultHost = "chatgpt", preserveToolD
6623
6664
  const [theme, setTheme] = useState(urlParams.theme ?? storedPrefs.theme ?? DEFAULT_THEME);
6624
6665
  const [displayMode, _setDisplayMode] = useState(urlParams.displayMode ?? storedPrefs.displayMode ?? DEFAULT_DISPLAY_MODE);
6625
6666
  const [locale, setLocale] = useState(urlParams.locale ?? storedPrefs.locale ?? "en-US");
6626
- const [containerHeight, setContainerHeight] = useState(void 0);
6627
- const [containerWidth, setContainerWidth] = useState(void 0);
6667
+ const [containerHeight, setContainerHeight] = useState(storedPrefs.containerHeight);
6668
+ const [containerWidth, setContainerWidth] = useState(storedPrefs.containerWidth);
6628
6669
  const [containerMaxHeight, setContainerMaxHeight] = useState(urlParams.containerMaxHeight ?? storedPrefs.containerMaxHeight);
6629
6670
  const [containerMaxWidth, setContainerMaxWidth] = useState(urlParams.containerMaxWidth ?? storedPrefs.containerMaxWidth);
6630
6671
  const [platform, setPlatform] = useState(urlParams.platform ?? storedPrefs.platform ?? DEFAULT_PLATFORM);
@@ -6636,7 +6677,7 @@ function useInspectorState({ simulations, defaultHost = "chatgpt", preserveToolD
6636
6677
  left: 0,
6637
6678
  right: 0
6638
6679
  });
6639
- const [timeZone, setTimeZone] = useState(() => Intl.DateTimeFormat().resolvedOptions().timeZone);
6680
+ const [timeZone, setTimeZone] = useState(() => storedPrefs.timeZone ?? Intl.DateTimeFormat().resolvedOptions().timeZone);
6640
6681
  const isFirstRender = useRef(true);
6641
6682
  useEffect(() => {
6642
6683
  if (isFirstRender.current) {
@@ -6644,29 +6685,32 @@ function useInspectorState({ simulations, defaultHost = "chatgpt", preserveToolD
6644
6685
  return;
6645
6686
  }
6646
6687
  if (autoRun) return;
6647
- try {
6648
- const prefs = {
6649
- theme,
6650
- locale,
6651
- displayMode,
6652
- containerMaxHeight,
6653
- containerMaxWidth,
6654
- safeAreaInsets,
6655
- activeHost,
6656
- platform,
6657
- hover,
6658
- touch,
6659
- screenWidth,
6660
- sidebarWidth,
6661
- rightSidebarWidth
6662
- };
6663
- localStorage.setItem(PREFS_KEY, JSON.stringify(prefs));
6664
- } catch {}
6688
+ writeStoredPrefs({
6689
+ ...readStoredPrefs(),
6690
+ theme,
6691
+ locale,
6692
+ displayMode,
6693
+ containerHeight,
6694
+ containerWidth,
6695
+ containerMaxHeight,
6696
+ containerMaxWidth,
6697
+ safeAreaInsets,
6698
+ activeHost,
6699
+ platform,
6700
+ hover,
6701
+ touch,
6702
+ screenWidth,
6703
+ sidebarWidth,
6704
+ rightSidebarWidth,
6705
+ timeZone
6706
+ });
6665
6707
  }, [
6666
6708
  autoRun,
6667
6709
  theme,
6668
6710
  locale,
6669
6711
  displayMode,
6712
+ containerHeight,
6713
+ containerWidth,
6670
6714
  containerMaxHeight,
6671
6715
  containerMaxWidth,
6672
6716
  safeAreaInsets,
@@ -6676,7 +6720,8 @@ function useInspectorState({ simulations, defaultHost = "chatgpt", preserveToolD
6676
6720
  touch,
6677
6721
  screenWidth,
6678
6722
  sidebarWidth,
6679
- rightSidebarWidth
6723
+ rightSidebarWidth,
6724
+ timeZone
6680
6725
  ]);
6681
6726
  const [measuredContentWidth, setMeasuredContentWidth] = useState(void 0);
6682
6727
  const handleContentWidthChange = useCallback((width) => {
@@ -7589,12 +7634,16 @@ var DOCS_BASE_URL = "https://sunpeak.ai/docs";
7589
7634
  var DEFAULT_MODEL_PROVIDERS = [{
7590
7635
  id: "openai",
7591
7636
  label: "OpenAI",
7592
- defaultModel: "gpt-4o"
7637
+ defaultModel: "gpt-5.5"
7593
7638
  }, {
7594
7639
  id: "anthropic",
7595
7640
  label: "Anthropic",
7596
- defaultModel: "claude-3-5-sonnet-20241022"
7641
+ defaultModel: "claude-sonnet-4-20250514"
7597
7642
  }];
7643
+ function createModelConversationId() {
7644
+ const random = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : Math.random().toString(36).slice(2);
7645
+ return `model-chat-${Date.now()}-${random}`;
7646
+ }
7598
7647
  function splitCssArgs(value) {
7599
7648
  const args = [];
7600
7649
  let depth = 0;
@@ -7687,6 +7736,7 @@ function Inspector({ children, app, simulations: initialSimulationsProp = EMPTY_
7687
7736
  autoRun: params.get("autoRun") === "true"
7688
7737
  };
7689
7738
  }, []);
7739
+ const storedPrefs = React.useMemo(() => initUrlParams.autoRun ? {} : readStoredPrefs(), [initUrlParams.autoRun]);
7690
7740
  const [selectedToolName, setSelectedToolName] = React.useState(() => {
7691
7741
  if (initUrlParams.tool && toolMap.has(initUrlParams.tool)) return initUrlParams.tool;
7692
7742
  if (initUrlParams.simulation) {
@@ -7736,7 +7786,7 @@ function Inspector({ children, app, simulations: initialSimulationsProp = EMPTY_
7736
7786
  const [oauthStatus, setOauthStatus] = React.useState("none");
7737
7787
  const [oauthError, setOauthError] = React.useState();
7738
7788
  const connection = useMcpConnection(isEmbedded ? void 0 : mcpServerUrl || void 0, inspectorApiBaseUrl);
7739
- const [prodResources, setProdResources] = React.useState(state.urlProdResources ?? defaultProdResources);
7789
+ const [prodResources, setProdResources] = React.useState(state.urlProdResources ?? storedPrefs.prodResources ?? defaultProdResources);
7740
7790
  const showSidebar = state.urlSidebar !== false;
7741
7791
  const showDevOverlay = state.urlDevOverlay !== false;
7742
7792
  const [isRunning, setIsRunning] = React.useState(false);
@@ -7758,6 +7808,15 @@ function Inspector({ children, app, simulations: initialSimulationsProp = EMPTY_
7758
7808
  if (requested && modelProviderOptions.some((provider) => provider.id === requested)) return requested;
7759
7809
  return modelProviderOptions[0]?.id ?? "openai";
7760
7810
  }, [modelChat?.defaultProvider, modelProviderOptions]);
7811
+ const initialModelProvider = React.useMemo(() => {
7812
+ const stored = storedPrefs.modelProvider;
7813
+ if (stored && modelProviderOptions.some((provider) => provider.id === stored)) return stored;
7814
+ return defaultModelProvider;
7815
+ }, [
7816
+ defaultModelProvider,
7817
+ modelProviderOptions,
7818
+ storedPrefs.modelProvider
7819
+ ]);
7761
7820
  const getDefaultModelId = React.useCallback((provider) => {
7762
7821
  const option = modelProviderOptions.find((item) => item.id === provider);
7763
7822
  const candidates = [
@@ -7769,8 +7828,20 @@ function Inspector({ children, app, simulations: initialSimulationsProp = EMPTY_
7769
7828
  if (providerModels.length === 0) return candidates.find(Boolean) ?? "";
7770
7829
  return candidates.find((model) => model && providerModels.includes(model)) ?? providerModels[0];
7771
7830
  }, [modelChat?.defaultModel, modelProviderOptions]);
7772
- const [modelProvider, setModelProvider] = React.useState(defaultModelProvider);
7773
- const [modelId, setModelId] = React.useState(() => getDefaultModelId(defaultModelProvider));
7831
+ const getInitialModelId = React.useCallback((provider) => {
7832
+ const stored = storedPrefs.modelId;
7833
+ if (storedPrefs.modelProvider && storedPrefs.modelProvider !== provider) return getDefaultModelId(provider);
7834
+ const providerModels = modelProviderOptions.find((item) => item.id === provider)?.models ?? [];
7835
+ if (stored && (providerModels.length === 0 || providerModels.includes(stored))) return stored;
7836
+ return getDefaultModelId(provider);
7837
+ }, [
7838
+ getDefaultModelId,
7839
+ modelProviderOptions,
7840
+ storedPrefs.modelId,
7841
+ storedPrefs.modelProvider
7842
+ ]);
7843
+ const [modelProvider, setModelProvider] = React.useState(initialModelProvider);
7844
+ const [modelId, setModelId] = React.useState(() => getInitialModelId(initialModelProvider));
7774
7845
  const [apiKeyDraft, setApiKeyDraft] = React.useState("");
7775
7846
  const [keyStatus, setKeyStatus] = React.useState({ hasKey: false });
7776
7847
  const [isKeyStatusLoading, setIsKeyStatusLoading] = React.useState(usesApiKeyUi);
@@ -7779,6 +7850,7 @@ function Inspector({ children, app, simulations: initialSimulationsProp = EMPTY_
7779
7850
  const [chatInput, setChatInput] = React.useState("");
7780
7851
  const [isChatting, setIsChatting] = React.useState(false);
7781
7852
  const [chatStatus, setChatStatus] = React.useState("");
7853
+ const modelConversationIdRef = React.useRef(createModelConversationId());
7782
7854
  const currentModelProvider = React.useMemo(() => modelProviderOptions.find((provider) => provider.id === modelProvider), [modelProvider, modelProviderOptions]);
7783
7855
  const selectedProviderModelOptions = React.useMemo(() => currentModelProvider?.models ?? [], [currentModelProvider?.models]);
7784
7856
  const modelCallableTools = React.useMemo(() => {
@@ -7789,6 +7861,25 @@ function Inspector({ children, app, simulations: initialSimulationsProp = EMPTY_
7789
7861
  }
7790
7862
  return Array.from(map.values());
7791
7863
  }, [simulations]);
7864
+ const isFirstInspectorPrefsRender = React.useRef(true);
7865
+ React.useEffect(() => {
7866
+ if (isFirstInspectorPrefsRender.current) {
7867
+ isFirstInspectorPrefsRender.current = false;
7868
+ return;
7869
+ }
7870
+ if (initUrlParams.autoRun) return;
7871
+ writeStoredPrefs({
7872
+ ...readStoredPrefs(),
7873
+ prodResources,
7874
+ modelProvider,
7875
+ modelId
7876
+ });
7877
+ }, [
7878
+ initUrlParams.autoRun,
7879
+ modelId,
7880
+ modelProvider,
7881
+ prodResources
7882
+ ]);
7792
7883
  React.useEffect(() => {
7793
7884
  setServerUrl(mcpServerUrl ?? "");
7794
7885
  }, [mcpServerUrl]);
@@ -7898,6 +7989,13 @@ function Inspector({ children, app, simulations: initialSimulationsProp = EMPTY_
7898
7989
  usesApiKeyUi,
7899
7990
  usesLocalModelEndpoints
7900
7991
  ]);
7992
+ const handleResetModelConversation = React.useCallback(() => {
7993
+ modelConversationIdRef.current = createModelConversationId();
7994
+ setChatMessages([]);
7995
+ setChatInput("");
7996
+ setChatStatus("");
7997
+ setIsChatting(false);
7998
+ }, []);
7901
7999
  React.useEffect(() => {
7902
8000
  state.setSelectedSimulationName(effectiveSimulationName);
7903
8001
  }, [effectiveSimulationName]);
@@ -8158,15 +8256,18 @@ function Inspector({ children, app, simulations: initialSimulationsProp = EMPTY_
8158
8256
  state.setToolResultJson("");
8159
8257
  state.setToolResultError("");
8160
8258
  setHasRun(false);
8259
+ const requestConversationId = modelConversationIdRef.current;
8161
8260
  try {
8162
8261
  const messages = nextMessages.map((message) => ({
8163
8262
  role: message.role,
8164
- content: message.content
8165
- }));
8263
+ content: message.content.trim()
8264
+ })).filter((message) => message.content.length > 0);
8166
8265
  let data;
8167
8266
  if (modelChatHandler) data = await modelChatHandler({
8267
+ conversationId: requestConversationId,
8168
8268
  provider: modelProvider,
8169
8269
  modelId,
8270
+ host: state.activeHost,
8170
8271
  messages,
8171
8272
  tools: modelCallableTools,
8172
8273
  appContext: state.modelAppContext ?? void 0
@@ -8177,8 +8278,10 @@ function Inspector({ children, app, simulations: initialSimulationsProp = EMPTY_
8177
8278
  method: "POST",
8178
8279
  headers: { "Content-Type": "application/json" },
8179
8280
  body: JSON.stringify({
8281
+ conversationId: requestConversationId,
8180
8282
  provider: modelProvider,
8181
8283
  modelId,
8284
+ host: state.activeHost,
8182
8285
  messages,
8183
8286
  appContext: state.modelAppContext ?? void 0
8184
8287
  })
@@ -8187,6 +8290,7 @@ function Inspector({ children, app, simulations: initialSimulationsProp = EMPTY_
8187
8290
  if (!res.ok || data.error) throw new Error(data.error ?? `Model request failed (${res.status})`);
8188
8291
  }
8189
8292
  if (data.error) throw new Error(data.error);
8293
+ if (requestConversationId !== modelConversationIdRef.current) return;
8190
8294
  let rendersApp = false;
8191
8295
  const toolCalls = data.toolCalls ?? [];
8192
8296
  for (let index = toolCalls.length - 1; index >= 0; index--) {
@@ -8224,6 +8328,7 @@ function Inspector({ children, app, simulations: initialSimulationsProp = EMPTY_
8224
8328
  });
8225
8329
  setChatStatus("");
8226
8330
  } catch (err) {
8331
+ if (requestConversationId !== modelConversationIdRef.current) return;
8227
8332
  const message = err instanceof Error ? err.message : String(err);
8228
8333
  setChatMessages((messages) => [...messages, {
8229
8334
  id: `assistant-error-${Date.now()}`,
@@ -8232,7 +8337,7 @@ function Inspector({ children, app, simulations: initialSimulationsProp = EMPTY_
8232
8337
  }]);
8233
8338
  setChatStatus(message);
8234
8339
  } finally {
8235
- setIsChatting(false);
8340
+ if (requestConversationId === modelConversationIdRef.current) setIsChatting(false);
8236
8341
  }
8237
8342
  }, [
8238
8343
  canUseModelChat,
@@ -8846,33 +8951,69 @@ function Inspector({ children, app, simulations: initialSimulationsProp = EMPTY_
8846
8951
  children: /* @__PURE__ */ jsxs("div", {
8847
8952
  className: "space-y-1",
8848
8953
  children: [/* @__PURE__ */ jsxs("div", {
8849
- className: "grid grid-cols-2 gap-2",
8850
- children: [/* @__PURE__ */ jsx(SidebarControl, {
8851
- label: "Provider",
8852
- children: /* @__PURE__ */ jsx(SidebarSelect, {
8853
- value: modelProvider,
8854
- onChange: handleModelProviderChange,
8855
- options: modelProviderOptions.map((provider) => ({
8856
- value: provider.id,
8857
- label: provider.label ?? provider.id
8858
- }))
8859
- })
8860
- }), /* @__PURE__ */ jsx(SidebarControl, {
8861
- label: "Model",
8862
- children: selectedProviderModelOptions.length > 0 ? /* @__PURE__ */ jsx(SidebarSelect, {
8863
- value: modelId,
8864
- onChange: setModelId,
8865
- options: selectedProviderModelOptions.map((model) => ({
8866
- value: model,
8867
- label: model
8868
- }))
8869
- }) : /* @__PURE__ */ jsx(SidebarInput, {
8870
- value: modelId,
8871
- onChange: setModelId,
8872
- applyOnBlur: true,
8873
- placeholder: getDefaultModelId(modelProvider)
8954
+ className: "grid grid-cols-[0.7fr_minmax(0,1fr)_1.75rem] items-end gap-2",
8955
+ children: [
8956
+ /* @__PURE__ */ jsx(SidebarControl, {
8957
+ label: "Provider",
8958
+ children: /* @__PURE__ */ jsx(SidebarSelect, {
8959
+ value: modelProvider,
8960
+ onChange: handleModelProviderChange,
8961
+ options: modelProviderOptions.map((provider) => ({
8962
+ value: provider.id,
8963
+ label: provider.label ?? provider.id
8964
+ }))
8965
+ })
8966
+ }),
8967
+ /* @__PURE__ */ jsx(SidebarControl, {
8968
+ label: "Model",
8969
+ children: selectedProviderModelOptions.length > 0 ? /* @__PURE__ */ jsx(SidebarSelect, {
8970
+ value: modelId,
8971
+ onChange: setModelId,
8972
+ options: selectedProviderModelOptions.map((model) => ({
8973
+ value: model,
8974
+ label: model
8975
+ }))
8976
+ }) : /* @__PURE__ */ jsx(SidebarInput, {
8977
+ value: modelId,
8978
+ onChange: setModelId,
8979
+ applyOnBlur: true,
8980
+ placeholder: getDefaultModelId(modelProvider)
8981
+ })
8982
+ }),
8983
+ /* @__PURE__ */ jsxs("div", {
8984
+ className: "group relative flex h-7 items-center self-end",
8985
+ children: [/* @__PURE__ */ jsx("button", {
8986
+ type: "button",
8987
+ onClick: handleResetModelConversation,
8988
+ disabled: chatMessages.length === 0 && !isChatting && !chatStatus,
8989
+ "aria-label": "Reset model conversation",
8990
+ "aria-describedby": "reset-model-conversation-tooltip",
8991
+ title: "Reset conversation",
8992
+ className: "flex h-7 w-7 cursor-pointer items-center justify-center rounded-full transition-colors disabled:cursor-not-allowed disabled:opacity-40",
8993
+ style: {
8994
+ backgroundColor: "var(--color-background-primary)",
8995
+ color: "var(--color-text-primary)"
8996
+ },
8997
+ children: /* @__PURE__ */ jsx("svg", {
8998
+ width: "18",
8999
+ height: "18",
9000
+ viewBox: "0 0 24 24",
9001
+ fill: "currentColor",
9002
+ "aria-hidden": "true",
9003
+ children: /* @__PURE__ */ jsx("path", { d: "M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-8 3.58-8 8s3.58 8 8 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h8V3z" })
9004
+ })
9005
+ }), /* @__PURE__ */ jsx("span", {
9006
+ id: "reset-model-conversation-tooltip",
9007
+ role: "tooltip",
9008
+ className: "pointer-events-none absolute right-0 top-full z-[1000] mt-1 hidden whitespace-nowrap rounded px-2 py-1 text-[11px] font-normal leading-tight group-focus-within:block group-hover:block",
9009
+ style: {
9010
+ backgroundColor: "var(--color-text-primary)",
9011
+ color: "var(--color-background-primary)"
9012
+ },
9013
+ children: "Reset conversation"
9014
+ })]
8874
9015
  })
8875
- })]
9016
+ ]
8876
9017
  }), usesApiKeyUi && /* @__PURE__ */ jsxs(SidebarControl, {
8877
9018
  label: "API Key",
8878
9019
  children: [/* @__PURE__ */ jsxs("div", {
@@ -9260,4 +9401,4 @@ function Inspector({ children, app, simulations: initialSimulationsProp = EMPTY_
9260
9401
  //#endregion
9261
9402
  export { cn as C, registerHostShell as S, extractResourceCSP as _, SidebarCollapsibleControl as a, getHostShell as b, SidebarSelect as c, SimpleSidebar as d, ThemeProvider as f, IframeResource as g, useInspectorState as h, SidebarCheckbox as i, SidebarTextarea as l, useMcpConnection as m, flattenAppToSimulations as n, SidebarControl as o, useThemeContext as p, resolveServerToolResult as r, SidebarInput as s, Inspector as t, SidebarToggle as u, McpAppHost as v, DEFAULT_STYLE_VARIABLES as w, getRegisteredHosts as x, SCREEN_WIDTHS as y };
9262
9403
 
9263
- //# sourceMappingURL=inspector-CiuT_2yA.js.map
9404
+ //# sourceMappingURL=inspector-BSha-CAW.js.map