@marimo-team/islands 0.23.7-dev9 → 0.23.7-dev90

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 (153) hide show
  1. package/dist/{ConnectedDataExplorerComponent-DnRhpPMJ.js → ConnectedDataExplorerComponent-2lBNiUv6.js} +13 -13
  2. package/dist/{ErrorBoundary-Da4UeYxT.js → ErrorBoundary-D3wrPNma.js} +1 -1
  3. package/dist/{any-language-editor-DDubl8YH.js → any-language-editor-VWs_7v27.js} +5 -5
  4. package/dist/assets/__vite-browser-external-CAdMKBac.js +1 -0
  5. package/dist/assets/worker-CpBbwbQo.js +73 -0
  6. package/dist/{button-CA5pI2YF.js → button-Dj4BTre0.js} +5 -0
  7. package/dist/{capabilities-6laDasij.js → capabilities-C9rrYCzf.js} +1 -1
  8. package/dist/{chat-ui-BmWZZ3mE.js → chat-ui-D3XBept8.js} +625 -233
  9. package/dist/{check-CFM2mVDr.js → check-BcUIXnUT.js} +1 -1
  10. package/dist/{code-visibility-CRHzv49w.js → code-visibility-C5NrPsUC.js} +11480 -1992
  11. package/dist/{copy-TGGAUEWp.js → copy-DLf4aN7I.js} +2 -2
  12. package/dist/{dist-ESg7xyoD.js → dist-D3ZI9nhS.js} +2 -2
  13. package/dist/{error-banner-DnBPzEWg.js → error-banner-CVkfBUT3.js} +2 -2
  14. package/dist/{esm-Dd1z1auZ.js → esm-CWp0KQeK.js} +1 -1
  15. package/dist/{extends-CzJgxo2J.js → extends-vAi97cpa.js} +4 -4
  16. package/dist/{formats-CgaK7Gmx.js → formats-Dsy9kkZu.js} +3 -3
  17. package/dist/{glide-data-editor-B-3A3G02.js → glide-data-editor-DucgdjRo.js} +9 -9
  18. package/dist/{html-to-image-BwZL1Pkk.js → html-to-image-CpggM7u1.js} +2667 -2408
  19. package/dist/{input-BAOe64zx.js → input-D4kjoQUB.js} +8 -6
  20. package/dist/{label-BCWi-Oqu.js → label-BLqV33b1.js} +2 -2
  21. package/dist/{loader-BvW0-YWZ.js → loader-Dr8Qem8p.js} +1 -1
  22. package/dist/main.js +1697 -10282
  23. package/dist/{mermaid-cXSZ1pfD.js → mermaid-DO-Daq7u.js} +5 -5
  24. package/dist/{process-output-lpVrk7d5.js → process-output-X8TR20AK.js} +3 -3
  25. package/dist/reveal-component-kMIwe09M.js +7447 -0
  26. package/dist/{spec-DSIuqd3f.js → spec-hVaaZsY5.js} +4 -4
  27. package/dist/{strings-B_FOH6eV.js → strings-BiIhGaI8.js} +4 -4
  28. package/dist/style.css +1 -1
  29. package/dist/{swiper-component-BHs0PWwp.js → swiper-component-DlD2GU2g.js} +2 -2
  30. package/dist/{toDate-CHtl9vts.js → toDate-CIpC_34u.js} +33 -20
  31. package/dist/{tooltip-B0mtKTXm.js → tooltip-DRaMBu06.js} +3 -3
  32. package/dist/{types-DBtDeUKD.js → types-Dzuoc3LN.js} +1 -1
  33. package/dist/{useAsyncData-B6hCGywC.js → useAsyncData-C56Khv_R.js} +1 -1
  34. package/dist/{useDateFormatter-B3mCQMP3.js → useDateFormatter-B_9k85Ex.js} +2 -2
  35. package/dist/{useDeepCompareMemoize-CmwDuYUH.js → useDeepCompareMemoize-Dt98v2ua.js} +1 -1
  36. package/dist/{useIframeCapabilities-DbdLoEDm.js → useIframeCapabilities-BkYHTrss.js} +1 -1
  37. package/dist/{useLifecycle-CjMjllqy.js → useLifecycle-BF6-z62y.js} +3 -3
  38. package/dist/{useTheme-CByZUW0p.js → useTheme-DykuNHR2.js} +2 -2
  39. package/dist/{vega-component-C2BYPkfd.js → vega-component-cSdqoAxe.js} +10 -10
  40. package/dist/{zod-BxdsqRPd.js → zod-BWkcDORu.js} +1 -1
  41. package/package.json +3 -3
  42. package/src/components/chat/chat-components.tsx +47 -0
  43. package/src/components/chat/chat-display.tsx +41 -7
  44. package/src/components/chat/chat-panel.tsx +37 -10
  45. package/src/components/chat/chat-utils.ts +42 -20
  46. package/src/components/chat/reasoning-accordion.tsx +14 -3
  47. package/src/components/chat/tool-call/shared.ts +13 -0
  48. package/src/components/chat/tool-call/tool-approval-card.tsx +62 -0
  49. package/src/components/chat/tool-call/tool-args.tsx +26 -0
  50. package/src/components/chat/tool-call/tool-call-view.tsx +99 -0
  51. package/src/components/chat/tool-call/tool-error-card.tsx +81 -0
  52. package/src/components/chat/tool-call/tool-history-row.tsx +153 -0
  53. package/src/components/chat/tool-call/tool-result.tsx +101 -0
  54. package/src/components/data-table/__tests__/column-header.test.ts +3 -1
  55. package/src/components/data-table/__tests__/column-header.test.tsx +308 -0
  56. package/src/components/data-table/__tests__/filter-by-values-picker.test.tsx +112 -0
  57. package/src/components/data-table/__tests__/filter-pill-editor.test.tsx +261 -0
  58. package/src/components/data-table/__tests__/filters.test.ts +196 -49
  59. package/src/components/data-table/charts/components/form-fields.tsx +1 -0
  60. package/src/components/data-table/column-header.tsx +349 -170
  61. package/src/components/data-table/date-filter-inputs.tsx +325 -0
  62. package/src/components/data-table/filter-by-values-picker.tsx +70 -9
  63. package/src/components/data-table/filter-pill-editor.tsx +410 -156
  64. package/src/components/data-table/filter-pills.tsx +69 -54
  65. package/src/components/data-table/filters.ts +218 -101
  66. package/src/components/data-table/header-items.tsx +8 -1
  67. package/src/components/data-table/operator-labels.ts +25 -0
  68. package/src/components/data-table/regex-input.tsx +61 -0
  69. package/src/components/dependency-graph/minimap-content.tsx +14 -3
  70. package/src/components/editor/actions/pair-with-agent-modal.tsx +140 -49
  71. package/src/components/editor/actions/useNotebookActions.tsx +3 -1
  72. package/src/components/editor/app-container.tsx +7 -1
  73. package/src/components/editor/chrome/panels/context-aware-panel/context-aware-panel.tsx +10 -2
  74. package/src/components/editor/chrome/wrapper/app-chrome.tsx +1 -0
  75. package/src/components/editor/chrome/wrapper/footer-items/backend-status.tsx +1 -1
  76. package/src/components/editor/chrome/wrapper/footer.tsx +4 -1
  77. package/src/components/editor/chrome/wrapper/panels.tsx +4 -1
  78. package/src/components/editor/chrome/wrapper/sidebar.tsx +4 -1
  79. package/src/components/editor/controls/Controls.tsx +11 -3
  80. package/src/components/editor/file-tree/file-explorer.tsx +12 -2
  81. package/src/components/editor/header/__tests__/status.test.tsx +108 -0
  82. package/src/components/editor/header/status.tsx +44 -10
  83. package/src/components/editor/navigation/__tests__/clipboard.test.ts +106 -0
  84. package/src/components/editor/navigation/__tests__/navigation.test.ts +70 -0
  85. package/src/components/editor/navigation/clipboard.ts +99 -25
  86. package/src/components/editor/navigation/navigation.ts +15 -1
  87. package/src/components/editor/notebook-cell.tsx +5 -0
  88. package/src/components/editor/output/console/ConsoleOutput.tsx +23 -5
  89. package/src/components/editor/output/console/__tests__/ConsoleOutput.test.tsx +114 -0
  90. package/src/components/editor/renderers/slides-layout/__tests__/compute-slide-cells.test.ts +5 -4
  91. package/src/components/editor/renderers/slides-layout/__tests__/plugin.test.ts +55 -15
  92. package/src/components/editor/renderers/slides-layout/plugin.tsx +8 -25
  93. package/src/components/editor/renderers/slides-layout/slides-layout.tsx +19 -6
  94. package/src/components/editor/renderers/slides-layout/types.ts +40 -31
  95. package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +1 -0
  96. package/src/components/home/components.tsx +6 -0
  97. package/src/components/pages/run-page.tsx +4 -1
  98. package/src/components/scratchpad/scratchpad.tsx +1 -0
  99. package/src/components/slides/__tests__/slide-notes.test.ts +131 -0
  100. package/src/components/slides/reveal-component.tsx +252 -147
  101. package/src/components/slides/slide-notes-editor.tsx +127 -0
  102. package/src/components/slides/slide-notes.ts +64 -0
  103. package/src/components/slides/slides.css +14 -0
  104. package/src/components/ui/combobox.tsx +24 -5
  105. package/src/components/ui/number-field.tsx +2 -0
  106. package/src/core/ai/tools/__tests__/registry.test.ts +10 -12
  107. package/src/core/ai/tools/registry.ts +9 -5
  108. package/src/core/cells/__tests__/cells.test.ts +187 -0
  109. package/src/core/cells/__tests__/pending-cut-service.test.tsx +123 -0
  110. package/src/core/cells/cells.ts +102 -17
  111. package/src/core/cells/document-changes.ts +6 -1
  112. package/src/core/cells/pending-cut-service.ts +55 -0
  113. package/src/core/cells/utils.ts +11 -0
  114. package/src/core/codemirror/cells/extensions.ts +10 -0
  115. package/src/core/codemirror/go-to-definition/__tests__/commands.test.ts +152 -0
  116. package/src/core/codemirror/go-to-definition/__tests__/utils.test.ts +99 -0
  117. package/src/core/codemirror/go-to-definition/commands.ts +382 -22
  118. package/src/core/codemirror/go-to-definition/utils.ts +23 -5
  119. package/src/core/edit-app.tsx +3 -2
  120. package/src/core/hotkeys/hotkeys.ts +5 -0
  121. package/src/core/islands/worker/worker.tsx +3 -2
  122. package/src/core/run-app.tsx +2 -1
  123. package/src/core/runtime/__tests__/runtime.test.ts +38 -17
  124. package/src/core/runtime/runtime.ts +57 -34
  125. package/src/core/wasm/__tests__/utils.test.ts +34 -0
  126. package/src/core/wasm/utils.ts +14 -0
  127. package/src/core/wasm/worker/bootstrap.ts +3 -2
  128. package/src/core/wasm/worker/worker.ts +3 -2
  129. package/src/core/websocket/__tests__/useMarimoKernelConnection.hook.test.tsx +156 -0
  130. package/src/core/websocket/__tests__/useMarimoKernelConnection.test.ts +101 -0
  131. package/src/core/websocket/transports/__tests__/ws.test.ts +125 -0
  132. package/src/core/websocket/transports/basic.ts +1 -1
  133. package/src/core/websocket/transports/ws.ts +96 -0
  134. package/src/core/websocket/useMarimoKernelConnection.tsx +133 -54
  135. package/src/core/websocket/useWebSocket.tsx +3 -15
  136. package/src/css/app/Cell.css +10 -0
  137. package/src/plugins/core/__test__/sanitize.test.ts +30 -0
  138. package/src/plugins/impl/DropdownPlugin.tsx +12 -1
  139. package/src/plugins/impl/MultiselectPlugin.tsx +4 -0
  140. package/src/plugins/impl/SearchableSelect.tsx +11 -1
  141. package/src/plugins/impl/TabsPlugin.tsx +35 -7
  142. package/src/plugins/impl/__tests__/DropdownPlugin.test.tsx +56 -0
  143. package/src/plugins/impl/__tests__/TabsPlugin.test.tsx +154 -0
  144. package/src/plugins/impl/data-frames/forms/__tests__/__snapshots__/form.test.tsx.snap +48 -36
  145. package/src/plugins/impl/data-frames/schema.ts +4 -1
  146. package/src/plugins/layout/DownloadPlugin.tsx +9 -7
  147. package/src/utils/__tests__/id-tree.test.ts +71 -0
  148. package/src/utils/download.ts +4 -2
  149. package/src/utils/id-tree.tsx +89 -0
  150. package/dist/assets/__vite-browser-external-rrUYDKRl.js +0 -1
  151. package/dist/assets/worker-Bfy15ViQ.js +0 -73
  152. package/dist/reveal-component-C97Ceb7e.js +0 -4863
  153. package/src/components/chat/tool-call-accordion.tsx +0 -247
@@ -1,9 +1,9 @@
1
1
  import { s as __toESM } from "./chunk-BNovOVIE.js";
2
- import { g as cn, l as useEventListener, t as Button } from "./button-CA5pI2YF.js";
2
+ import { g as cn, l as useEventListener, t as Button } from "./button-Dj4BTre0.js";
3
3
  import { t as require_react } from "./react-DA-nE2FX.js";
4
4
  import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
5
5
  import { t as require_jsx_runtime } from "./jsx-runtime-COBk7ree.js";
6
- import { t as useIframeCapabilities } from "./useIframeCapabilities-DbdLoEDm.js";
6
+ import { t as useIframeCapabilities } from "./useIframeCapabilities-BkYHTrss.js";
7
7
  var import_compiler_runtime = require_compiler_runtime(), import_react = /* @__PURE__ */ __toESM(require_react(), 1);
8
8
  function isObject$2(t2) {
9
9
  return typeof t2 == "object" && !!t2 && "constructor" in t2 && t2.constructor === Object;
@@ -2,11 +2,11 @@ var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
  import { t as __commonJSMin } from "./chunk-BNovOVIE.js";
5
- import { _ as Logger } from "./button-CA5pI2YF.js";
5
+ import { _ as Logger } from "./button-Dj4BTre0.js";
6
6
  import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
7
- import { u as createLucideIcon } from "./dist-ESg7xyoD.js";
7
+ import { u as createLucideIcon } from "./dist-D3ZI9nhS.js";
8
8
  import { r as KnownQueryParams } from "./constants-D0gkYoE2.js";
9
- import { f as waitFor, p as isIslands, u as store, y as atom } from "./useTheme-CByZUW0p.js";
9
+ import { f as waitFor, p as isIslands, u as store, y as atom } from "./useTheme-DykuNHR2.js";
10
10
  import { t as invariant } from "./invariant-UcGKQEhF.js";
11
11
  var CircleQuestionMark = createLucideIcon("circle-question-mark", [
12
12
  ["circle", {
@@ -512,15 +512,21 @@ var RuntimeManager = class {
512
512
  get isSameOrigin() {
513
513
  return this.httpURL.origin === window.location.origin;
514
514
  }
515
- formatHttpURL(e, b, x = true) {
516
- e || (e = "");
515
+ get isServerless() {
516
+ return isWasm() || isIslands() || isStaticNotebook();
517
+ }
518
+ formatHttpURL({ path: e = "", searchParams: b, restrictToKnownQueryParams: x = true }) {
517
519
  let S = this.httpURL, w = new URLSearchParams(window.location.search);
518
520
  if (b) for (let [e2, x2] of b.entries()) S.searchParams.set(e2, x2);
519
521
  for (let [e2, b2] of w.entries()) x && !Object.values(KnownQueryParams).includes(e2) || S.searchParams.set(e2, b2);
520
522
  return S.pathname = `${S.pathname.replace(/\/$/, "")}/${e.replace(/^\//, "")}`, S.hash = "", S;
521
523
  }
522
524
  formatWsURL(e, b) {
523
- let x = this.formatHttpURL(e, b, false);
525
+ let x = this.formatHttpURL({
526
+ path: e,
527
+ searchParams: b,
528
+ restrictToKnownQueryParams: false
529
+ });
524
530
  return !this.isSameOrigin && this.config.authToken && x.searchParams.set(KnownQueryParams.accessToken, this.config.authToken), asWsUrl(x.toString());
525
531
  }
526
532
  getWsURL(e) {
@@ -546,26 +552,33 @@ var RuntimeManager = class {
546
552
  return this.formatWsURL(`/lsp/${e}`);
547
553
  }
548
554
  getAiURL(e) {
549
- return this.formatHttpURL(`/api/ai/${e}`);
555
+ return this.formatHttpURL({ path: `/api/ai/${e}` });
550
556
  }
551
557
  healthURL() {
552
- return this.formatHttpURL("/health");
558
+ return this.formatHttpURL({ path: "/health" });
553
559
  }
554
- async isHealthy() {
555
- if (isWasm() || isIslands() || isStaticNotebook()) return true;
560
+ async fetchHealth() {
556
561
  try {
557
- let e = await fetch(this.healthURL().toString());
558
- if (e.redirected) {
559
- Logger.debug(`Runtime redirected to ${e.url}`);
560
- let x2 = new URL(e.url);
561
- x2.pathname = x2.pathname.replace(/\/health$/, ""), this.config.url = x2.toString();
562
- }
563
- let x = e.ok;
564
- return x && this.setDOMBaseUri(this.config.url), x;
562
+ return await fetch(this.healthURL().toString());
565
563
  } catch (e) {
566
- return Logger.error(`Failed to check health: ${e instanceof Error ? e.message : "Unknown error"}`, { cause: e }), false;
564
+ return Logger.error(`Failed to check health: ${e instanceof Error ? e.message : "Unknown error"}`, { cause: e }), null;
567
565
  }
568
566
  }
567
+ async reconcileFromHealth() {
568
+ if (this.isServerless) return true;
569
+ let e = await this.fetchHealth();
570
+ if (!e) return false;
571
+ if (e.redirected) {
572
+ Logger.debug(`Runtime redirected to ${e.url}`);
573
+ let x = new URL(e.url);
574
+ x.pathname = x.pathname.replace(/\/health$/, ""), this.config.url = x.toString();
575
+ }
576
+ return e.ok && this.setDOMBaseUri(this.config.url), e.ok;
577
+ }
578
+ async probeHealth() {
579
+ var _a;
580
+ return this.isServerless ? true : ((_a = await this.fetchHealth()) == null ? void 0 : _a.ok) ?? false;
581
+ }
569
582
  setDOMBaseUri(e) {
570
583
  e = e.split("?", 1)[0], e.endsWith("/") || (e += "/");
571
584
  let b = document.querySelector("base");
@@ -574,7 +587,7 @@ var RuntimeManager = class {
574
587
  async init(e) {
575
588
  Logger.debug("Initializing runtime...");
576
589
  let x = 0;
577
- for (; !await this.isHealthy(); ) {
590
+ for (; !await this.reconcileFromHealth(); ) {
578
591
  if (x >= 25) {
579
592
  Logger.error("Failed to connect after 25 retries"), this.initialHealthyCheck.reject(/* @__PURE__ */ Error("Failed to connect after 25 retries"));
580
593
  return;
@@ -1,10 +1,10 @@
1
1
  import { s as __toESM } from "./chunk-BNovOVIE.js";
2
- import { f as createSlottable, g as cn, m as useComposedRefs } from "./button-CA5pI2YF.js";
2
+ import { f as createSlottable, g as cn, m as useComposedRefs } from "./button-Dj4BTre0.js";
3
3
  import { t as require_react } from "./react-DA-nE2FX.js";
4
4
  import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
5
- import { a as createPopperScope, i as Root2, n as Arrow, r as Content, s as Root, t as Anchor } from "./dist-ESg7xyoD.js";
5
+ import { a as createPopperScope, i as Root2, n as Arrow, r as Content, s as Root, t as Anchor } from "./dist-D3ZI9nhS.js";
6
6
  import { t as require_jsx_runtime } from "./jsx-runtime-COBk7ree.js";
7
- import { $ as StyleNamespace, X as withFullScreenAsRoot, Z as withSmartCollisionBoundary, _t as Primitive, dt as Presence, ft as useControllableState, gt as createContextScope, it as Portal, mt as composeEventHandlers, st as DismissableLayer, ut as useId } from "./zod-BxdsqRPd.js";
7
+ import { $ as StyleNamespace, X as withFullScreenAsRoot, Z as withSmartCollisionBoundary, _t as Primitive, dt as Presence, ft as useControllableState, gt as createContextScope, it as Portal, mt as composeEventHandlers, st as DismissableLayer, ut as useId } from "./zod-BWkcDORu.js";
8
8
  var import_react = /* @__PURE__ */ __toESM(require_react(), 1), import_jsx_runtime = /* @__PURE__ */ __toESM(require_jsx_runtime(), 1), [createTooltipContext, createTooltipScope] = createContextScope("Tooltip", [createPopperScope]), usePopperScope = createPopperScope(), PROVIDER_NAME = "TooltipProvider", DEFAULT_DELAY_DURATION = 700, TOOLTIP_OPEN = "tooltip.open", [TooltipProviderContextProvider, useTooltipProviderContext] = createTooltipContext(PROVIDER_NAME), TooltipProvider$1 = (n) => {
9
9
  let { __scopeTooltip: j, delayDuration: M = DEFAULT_DELAY_DURATION, skipDelayDuration: N = 300, disableHoverableContent: P = false, children: F } = n, I = import_react.useRef(true), L = import_react.useRef(false), R = import_react.useRef(0);
10
10
  return import_react.useEffect(() => {
@@ -1,4 +1,4 @@
1
- import { u as createLucideIcon } from "./dist-ESg7xyoD.js";
1
+ import { u as createLucideIcon } from "./dist-D3ZI9nhS.js";
2
2
  var Pencil = createLucideIcon("pencil", [["path", {
3
3
  d: "M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z",
4
4
  key: "1a8usu"
@@ -1,7 +1,7 @@
1
1
  import { s as __toESM } from "./chunk-BNovOVIE.js";
2
2
  import { t as require_react } from "./react-DA-nE2FX.js";
3
3
  import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
4
- import { w as useEvent_default } from "./useTheme-CByZUW0p.js";
4
+ import { w as useEvent_default } from "./useTheme-DykuNHR2.js";
5
5
  import { t as invariant } from "./invariant-UcGKQEhF.js";
6
6
  var import_compiler_runtime = require_compiler_runtime(), import_react = /* @__PURE__ */ __toESM(require_react(), 1), Result = {
7
7
  error(e, s) {
@@ -1,7 +1,7 @@
1
1
  import { s as __toESM } from "./chunk-BNovOVIE.js";
2
2
  import { t as require_react } from "./react-DA-nE2FX.js";
3
- import { u as createLucideIcon } from "./dist-ESg7xyoD.js";
4
- import { T as $18f2051aff69b9bf$export$43bb16f9c6d9e3f7 } from "./strings-B_FOH6eV.js";
3
+ import { u as createLucideIcon } from "./dist-D3ZI9nhS.js";
4
+ import { T as $18f2051aff69b9bf$export$43bb16f9c6d9e3f7 } from "./strings-BiIhGaI8.js";
5
5
  var ChartPie = createLucideIcon("chart-pie", [["path", {
6
6
  d: "M21 12c.552 0 1.005-.449.95-.998a10 10 0 0 0-8.953-8.951c-.55-.055-.998.398-.998.95v8a1 1 0 0 0 1 1z",
7
7
  key: "pzmjnu"
@@ -1,6 +1,6 @@
1
1
  import { s as __toESM } from "./chunk-BNovOVIE.js";
2
2
  import { t as require_react } from "./react-DA-nE2FX.js";
3
- import { C as dequal } from "./useTheme-CByZUW0p.js";
3
+ import { C as dequal } from "./useTheme-DykuNHR2.js";
4
4
  var import_react = /* @__PURE__ */ __toESM(require_react(), 1);
5
5
  function useDeepCompareMemoize(e) {
6
6
  let i = import_react.useRef(e);
@@ -1,7 +1,7 @@
1
1
  import { s as __toESM } from "./chunk-BNovOVIE.js";
2
2
  import { t as require_react } from "./react-DA-nE2FX.js";
3
3
  import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
4
- import { t as getIframeCapabilities } from "./capabilities-6laDasij.js";
4
+ import { t as getIframeCapabilities } from "./capabilities-C9rrYCzf.js";
5
5
  var import_compiler_runtime = require_compiler_runtime();
6
6
  require_react();
7
7
  function useIframeCapabilities() {
@@ -1,10 +1,10 @@
1
1
  import { s as __toESM } from "./chunk-BNovOVIE.js";
2
- import { _ as Logger, g as cn, r as cva } from "./button-CA5pI2YF.js";
2
+ import { _ as Logger, g as cn, r as cva } from "./button-Dj4BTre0.js";
3
3
  import { t as require_react } from "./react-DA-nE2FX.js";
4
4
  import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
5
- import { u as createLucideIcon } from "./dist-ESg7xyoD.js";
5
+ import { u as createLucideIcon } from "./dist-D3ZI9nhS.js";
6
6
  import { t as require_jsx_runtime } from "./jsx-runtime-COBk7ree.js";
7
- import { _ as useSetAtom, y as atom } from "./useTheme-CByZUW0p.js";
7
+ import { _ as useSetAtom, y as atom } from "./useTheme-DykuNHR2.js";
8
8
  var Calendar = createLucideIcon("calendar", [
9
9
  ["path", {
10
10
  d: "M8 2v4",
@@ -1,8 +1,8 @@
1
1
  import { s as __toESM } from "./chunk-BNovOVIE.js";
2
- import { _ as Logger, a as OverridingHotkeyProvider, s as resolvePlatform } from "./button-CA5pI2YF.js";
2
+ import { _ as Logger, a as OverridingHotkeyProvider, s as resolvePlatform } from "./button-Dj4BTre0.js";
3
3
  import { t as require_react } from "./react-DA-nE2FX.js";
4
4
  import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
5
- import { A as looseObject, B as union, I as record, N as number, P as object, R as string, T as boolean, b as _enum, w as array } from "./zod-BxdsqRPd.js";
5
+ import { A as looseObject, B as union, I as record, N as number, P as object, R as string, T as boolean, b as _enum, w as array } from "./zod-BWkcDORu.js";
6
6
  import { t as merge_default } from "./merge-CHn7Yx0N.js";
7
7
  var import_react = /* @__PURE__ */ __toESM(require_react()), useInsertionEffect = typeof window < "u" ? import_react.useInsertionEffect || import_react.useLayoutEffect : () => {
8
8
  };
@@ -1,24 +1,24 @@
1
1
  import { s as __toESM } from "./chunk-BNovOVIE.js";
2
- import { _ as Logger, c as Objects, g as cn, h as Events } from "./button-CA5pI2YF.js";
2
+ import { _ as Logger, c as Objects, g as cn, h as Events } from "./button-Dj4BTre0.js";
3
3
  import { t as require_react } from "./react-DA-nE2FX.js";
4
4
  import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
5
- import { c as asRemoteURL, v as CircleQuestionMark } from "./toDate-CHtl9vts.js";
5
+ import { c as asRemoteURL, v as CircleQuestionMark } from "./toDate-CIpC_34u.js";
6
6
  import "./react-dom-BWRJ_g_k.js";
7
7
  import { t as require_jsx_runtime } from "./jsx-runtime-COBk7ree.js";
8
- import "./zod-BxdsqRPd.js";
9
- import { n as ErrorBanner } from "./error-banner-DnBPzEWg.js";
10
- import { t as Tooltip } from "./tooltip-B0mtKTXm.js";
8
+ import "./zod-BWkcDORu.js";
9
+ import { n as ErrorBanner } from "./error-banner-CVkfBUT3.js";
10
+ import { t as Tooltip } from "./tooltip-DRaMBu06.js";
11
11
  import { i as debounce_default } from "./constants-D0gkYoE2.js";
12
- import { n as useTheme, w as useEvent_default } from "./useTheme-CByZUW0p.js";
12
+ import { n as useTheme, w as useEvent_default } from "./useTheme-DykuNHR2.js";
13
13
  import { s as uniq } from "./arrays-CldYf7p7.js";
14
- import { a as isValid, i as AlertTitle, n as Alert, t as arrow } from "./formats-CgaK7Gmx.js";
14
+ import { a as isValid, i as AlertTitle, n as Alert, t as arrow } from "./formats-Dsy9kkZu.js";
15
15
  import { n as formats } from "./vega-loader.browser-3_z8GoFC.js";
16
- import { a as getContainerWidth, n as vegaLoadData, s as tooltipHandler } from "./loader-BvW0-YWZ.js";
17
- import { t as useAsyncData } from "./useAsyncData-B6hCGywC.js";
16
+ import { a as getContainerWidth, n as vegaLoadData, s as tooltipHandler } from "./loader-Dr8Qem8p.js";
17
+ import { t as useAsyncData } from "./useAsyncData-C56Khv_R.js";
18
18
  import { t as j } from "./react-vega-Dh6-UKKe.js";
19
19
  import "./defaultLocale-BpsHxBd7.js";
20
20
  import "./defaultLocale-DoeErsX2.js";
21
- import { t as useDeepCompareMemoize } from "./useDeepCompareMemoize-CmwDuYUH.js";
21
+ import { t as useDeepCompareMemoize } from "./useDeepCompareMemoize-Dt98v2ua.js";
22
22
  import { t as Semaphore } from "./semaphore-CNDGTzkX.js";
23
23
  var import_compiler_runtime = require_compiler_runtime(), import_react = /* @__PURE__ */ __toESM(require_react(), 1);
24
24
  function fixRelativeUrl(e) {
@@ -1,5 +1,5 @@
1
1
  import { r as __export, s as __toESM } from "./chunk-BNovOVIE.js";
2
- import { d as createSlot, l as useEventListener, m as useComposedRefs } from "./button-CA5pI2YF.js";
2
+ import { d as createSlot, l as useEventListener, m as useComposedRefs } from "./button-Dj4BTre0.js";
3
3
  import { t as require_react } from "./react-DA-nE2FX.js";
4
4
  import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
5
5
  import { t as require_react_dom } from "./react-dom-BWRJ_g_k.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.23.7-dev9",
3
+ "version": "0.23.7-dev90",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -21,7 +21,7 @@
21
21
  "./unstable_internal/*": "./src/*"
22
22
  },
23
23
  "dependencies": {
24
- "@ai-sdk/react": "^3.0.131",
24
+ "@ai-sdk/react": "^3.0.134",
25
25
  "@anywidget/types": "^0.2.0",
26
26
  "@codemirror/autocomplete": "^6.20.1",
27
27
  "@codemirror/commands": "^6.10.2",
@@ -120,7 +120,7 @@
120
120
  "lz-string": "^1.5.0",
121
121
  "marked": "^15.0.12",
122
122
  "mermaid": "^11.12.3",
123
- "partysocket": "1.1.10",
123
+ "partysocket": "1.1.13",
124
124
  "path-to-regexp": "^8.4.0",
125
125
  "plotly.js": "^3.3.1",
126
126
  "pyodide": "0.27.7",
@@ -13,6 +13,7 @@ import {
13
13
  } from "lucide-react";
14
14
  import { useState } from "react";
15
15
  import { cn } from "@/utils/cn";
16
+ import { isUrl } from "@/utils/urls";
16
17
  import { Spinner } from "../icons/spinner";
17
18
  import { Button } from "../ui/button";
18
19
  import { Input } from "../ui/input";
@@ -182,3 +183,49 @@ function renderFileIcon(file: File): React.ReactNode {
182
183
 
183
184
  return <FileIcon className={classNames} />;
184
185
  }
186
+
187
+ export const SourceChip = ({
188
+ icon,
189
+ title,
190
+ subtitle,
191
+ href,
192
+ }: {
193
+ icon: React.ReactNode;
194
+ title: string;
195
+ subtitle?: string;
196
+ href?: string;
197
+ }) => {
198
+ const content = (
199
+ <>
200
+ {icon}
201
+ <span className="truncate font-medium">{title}</span>
202
+ {subtitle && <span className="truncate opacity-70">({subtitle})</span>}
203
+ </>
204
+ );
205
+
206
+ const className =
207
+ "inline-flex max-w-full items-center gap-1.5 rounded-md border bg-muted/50 px-2 py-1 my-1 text-xs text-muted-foreground";
208
+
209
+ // Only treat absolute http(s) URLs as safe to render as a clickable link.
210
+ // Source URLs come from model output (e.g. citations) and could otherwise
211
+ // smuggle in `javascript:`/`data:` schemes.
212
+ if (href && isUrl(href)) {
213
+ return (
214
+ <a
215
+ href={href}
216
+ target="_blank"
217
+ rel="noopener noreferrer"
218
+ title={subtitle ?? title}
219
+ className={`${className} hover:bg-muted hover:text-foreground transition-colors`}
220
+ >
221
+ {content}
222
+ </a>
223
+ );
224
+ }
225
+
226
+ return (
227
+ <div className={className} title={subtitle ?? title}>
228
+ {content}
229
+ </div>
230
+ );
231
+ };
@@ -1,23 +1,31 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
- import type { DataUIPart, ToolUIPart, UIMessage } from "ai";
3
+ import type {
4
+ ChatAddToolApproveResponseFunction,
5
+ DataUIPart,
6
+ ToolUIPart,
7
+ UIMessage,
8
+ } from "ai";
9
+ import { ExternalLinkIcon, FileTextIcon } from "lucide-react";
4
10
  import React from "react";
5
11
  import { renderHTML } from "@/plugins/core/RenderHTML";
6
12
  import { logNever } from "@/utils/assertNever";
7
13
  import { Logger } from "@/utils/Logger";
8
14
  import { MarkdownRenderer } from "../markdown/markdown-renderer";
9
- import { AttachmentRenderer } from "./chat-components";
15
+ import { AttachmentRenderer, SourceChip } from "./chat-components";
10
16
  import { ReasoningAccordion } from "./reasoning-accordion";
11
- import { ToolCallAccordion } from "./tool-call-accordion";
17
+ import { ToolCallView } from "./tool-call/tool-call-view";
12
18
 
13
19
  export const renderUIMessage = ({
14
20
  message,
15
21
  isStreamingReasoning,
16
22
  isLast,
23
+ addToolApprovalResponse,
17
24
  }: {
18
25
  message: UIMessage;
19
26
  isStreamingReasoning: boolean;
20
27
  isLast: boolean;
28
+ addToolApprovalResponse?: ChatAddToolApproveResponseFunction;
21
29
  }) => {
22
30
  return (
23
31
  <>{message.parts.map((part, index) => renderUIMessagePart(part, index))}</>
@@ -29,14 +37,18 @@ export const renderUIMessage = ({
29
37
  ) {
30
38
  if (isToolPart(part)) {
31
39
  return (
32
- <ToolCallAccordion
40
+ <ToolCallView
33
41
  key={index}
34
42
  index={index}
35
43
  toolName={part.type}
36
44
  result={part.output}
45
+ errorText={part.state === "output-error" ? part.errorText : undefined}
37
46
  className="my-2"
38
47
  state={part.state}
39
48
  input={part.input}
49
+ approval={part.approval}
50
+ onApprove={addToolApprovalResponse}
51
+ isLive={isLast}
40
52
  />
41
53
  );
42
54
  }
@@ -69,7 +81,7 @@ export const renderUIMessage = ({
69
81
  isStreaming={
70
82
  isStreamingReasoning &&
71
83
  isLast &&
72
- // If there are multiple reasoning parts, only show the last one
84
+ // If there are multiple reasoning parts, only stream the last one
73
85
  index === (message.parts.length || 0) - 1
74
86
  }
75
87
  />
@@ -78,18 +90,40 @@ export const renderUIMessage = ({
78
90
  return <AttachmentRenderer attachment={part} key={index} />;
79
91
  case "dynamic-tool":
80
92
  return (
81
- <ToolCallAccordion
93
+ <ToolCallView
82
94
  key={index}
83
95
  toolName={part.toolName}
84
96
  result={part.output}
97
+ errorText={
98
+ part.state === "output-error" ? part.errorText : undefined
99
+ }
85
100
  state={part.state}
86
101
  input={part.input}
102
+ approval={part.approval}
103
+ onApprove={addToolApprovalResponse}
104
+ isLive={isLast}
87
105
  />
88
106
  );
89
107
  case "source-document":
108
+ return (
109
+ <SourceChip
110
+ key={index}
111
+ icon={<FileTextIcon className="h-3 w-3 shrink-0" />}
112
+ title={part.title}
113
+ subtitle={part.filename}
114
+ />
115
+ );
90
116
  case "source-url":
117
+ return (
118
+ <SourceChip
119
+ key={index}
120
+ icon={<ExternalLinkIcon className="h-3 w-3 shrink-0" />}
121
+ title={part.title ?? part.url}
122
+ subtitle={part.title ? part.url : undefined}
123
+ href={part.url}
124
+ />
125
+ );
91
126
  case "step-start":
92
- Logger.debug("Found non-renderable part", part);
93
127
  return null;
94
128
  default:
95
129
  logNever(part);
@@ -4,7 +4,13 @@ import type { UIMessage } from "@ai-sdk/react";
4
4
  import { useChat } from "@ai-sdk/react";
5
5
  import { storePrompt } from "@marimo-team/codemirror-ai";
6
6
  import type { ReactCodeMirrorRef } from "@uiw/react-codemirror";
7
- import { DefaultChatTransport, type FileUIPart, type TextUIPart } from "ai";
7
+ import {
8
+ type ChatAddToolApproveResponseFunction,
9
+ DefaultChatTransport,
10
+ type FileUIPart,
11
+ safeValidateUIMessages,
12
+ type TextUIPart,
13
+ } from "ai";
8
14
  import { useAtom, useAtomValue, useSetAtom, useStore } from "jotai";
9
15
  import {
10
16
  BotMessageSquareIcon,
@@ -131,10 +137,18 @@ interface ChatMessageProps {
131
137
  onEdit: (index: number, newValue: string) => void;
132
138
  isStreamingReasoning: boolean;
133
139
  isLast: boolean;
140
+ addToolApprovalResponse?: ChatAddToolApproveResponseFunction;
134
141
  }
135
142
 
136
143
  const ChatMessageDisplay: React.FC<ChatMessageProps> = memo(
137
- ({ message, index, onEdit, isStreamingReasoning, isLast }) => {
144
+ ({
145
+ message,
146
+ index,
147
+ onEdit,
148
+ isStreamingReasoning,
149
+ isLast,
150
+ addToolApprovalResponse,
151
+ }) => {
138
152
  const renderUserMessage = (message: UIMessage) => {
139
153
  const textParts = message.parts?.filter(
140
154
  (p): p is TextUIPart => p.type === "text",
@@ -181,7 +195,12 @@ const ChatMessageDisplay: React.FC<ChatMessageProps> = memo(
181
195
  <div className="absolute right-1 top-1 opacity-0 group-hover:opacity-100 transition-opacity">
182
196
  <CopyClipboardIcon className="h-3 w-3" value={content || ""} />
183
197
  </div>
184
- {renderUIMessage({ message, isStreamingReasoning, isLast })}
198
+ {renderUIMessage({
199
+ message,
200
+ isStreamingReasoning,
201
+ isLast,
202
+ addToolApprovalResponse,
203
+ })}
185
204
  </div>
186
205
  );
187
206
  };
@@ -458,6 +477,7 @@ const ChatPanelBody = () => {
458
477
  regenerate,
459
478
  stop,
460
479
  addToolOutput,
480
+ addToolApprovalResponse,
461
481
  id: chatId,
462
482
  } = useChat({
463
483
  id: activeChatId,
@@ -467,6 +487,19 @@ const ChatPanelBody = () => {
467
487
  api: runtimeManager.getAiURL("chat").toString(),
468
488
  headers: () => runtimeManager.headers(),
469
489
  prepareSendMessagesRequest: async (options) => {
490
+ // Canary: flag outgoing messages that don't match the AI SDK's own
491
+ // schema. The server-side sanitizer in `_pydantic_ai_utils.py` corrects these before validation;
492
+ // this log surfaces drift early without affecting the request.
493
+ const validation = await safeValidateUIMessages({
494
+ messages: options.messages,
495
+ });
496
+ if (!validation.success) {
497
+ Logger.debug(
498
+ "Outgoing chat messages failed AI SDK schema validation",
499
+ validation.error,
500
+ );
501
+ }
502
+
470
503
  const completionBody = await buildCompletionRequestBody(
471
504
  options.messages,
472
505
  );
@@ -495,13 +528,6 @@ const ChatPanelBody = () => {
495
528
  });
496
529
  },
497
530
  onToolCall: async ({ toolCall }) => {
498
- // Dynamic tool calls will throw an error for toolName
499
- // https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-tool-usage#client-side-page
500
- if (toolCall.dynamic) {
501
- Logger.debug("Skipping dynamic tool call", toolCall);
502
- return;
503
- }
504
-
505
531
  await handleToolCall({
506
532
  invokeAiTool,
507
533
  addToolOutput,
@@ -712,6 +738,7 @@ const ChatPanelBody = () => {
712
738
  onEdit={handleMessageEdit}
713
739
  isStreamingReasoning={isStreamingReasoning}
714
740
  isLast={idx === messages.length - 1}
741
+ addToolApprovalResponse={addToolApprovalResponse}
715
742
  />
716
743
  ))}
717
744
 
@@ -1,11 +1,12 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
3
  import type { components } from "@marimo-team/marimo-api";
4
- import type {
5
- ChatAddToolOutputFunction,
6
- FileUIPart,
7
- ToolUIPart,
8
- UIMessage,
4
+ import {
5
+ type ChatAddToolOutputFunction,
6
+ type FileUIPart,
7
+ isToolUIPart,
8
+ type ToolUIPart,
9
+ type UIMessage,
9
10
  } from "ai";
10
11
  import { useState } from "react";
11
12
  import useEvent from "react-use-event-hook";
@@ -16,6 +17,7 @@ import type {
16
17
  InvokeAiToolRequest,
17
18
  InvokeAiToolResponse,
18
19
  } from "@/core/network/types";
20
+ import { logNever } from "@/utils/assertNever";
19
21
  import { blobToString } from "@/utils/fileToBase64";
20
22
  import { Logger } from "@/utils/Logger";
21
23
  import { getAICompletionBodyWithAttachments } from "../editor/ai/completion-utils";
@@ -134,11 +136,11 @@ export async function handleToolCall({
134
136
  try {
135
137
  if (FRONTEND_TOOL_REGISTRY.has(toolCall.toolName)) {
136
138
  // Invoke the frontend tool
137
- const response = await FRONTEND_TOOL_REGISTRY.invoke(
138
- toolCall.toolName,
139
- toolCall.input,
140
- toolContext,
141
- );
139
+ const response = await FRONTEND_TOOL_REGISTRY.invoke({
140
+ toolName: toolCall.toolName,
141
+ rawArgs: toolCall.input,
142
+ toolContext: toolContext,
143
+ });
142
144
  addToolOutput({
143
145
  tool: toolCall.toolName,
144
146
  toolCallId: toolCall.toolCallId,
@@ -166,9 +168,33 @@ export async function handleToolCall({
166
168
  }
167
169
  }
168
170
 
171
+ /**
172
+ * Returns true if a tool call is "ready to be sent back to the server" — i.e.
173
+ * either it has reached a terminal output state, or the user has just supplied
174
+ * an approval response that the server hasn't seen yet.
175
+ */
176
+ function isToolCallReadyToSend(state: ToolUIPart["state"]): boolean {
177
+ switch (state) {
178
+ case "output-available":
179
+ case "output-error":
180
+ case "output-denied":
181
+ case "approval-responded":
182
+ return true;
183
+ case "input-streaming":
184
+ case "input-available":
185
+ case "approval-requested":
186
+ return false;
187
+ default:
188
+ logNever(state);
189
+ return false;
190
+ }
191
+ }
192
+
169
193
  /**
170
194
  * Checks if we should send a message automatically based on the messages.
171
- * We only want to send a message if all tool calls are completed and there is no reply yet.
195
+ * We auto-send when every tool call on the last assistant message has either
196
+ * finished (output-available/error/denied) or has just received a user
197
+ * approval response, and the assistant hasn't replied yet.
172
198
  */
173
199
  export function hasPendingToolCalls(messages: UIMessage[]): boolean {
174
200
  if (messages.length === 0) {
@@ -188,17 +214,14 @@ export function hasPendingToolCalls(messages: UIMessage[]): boolean {
188
214
  return false;
189
215
  }
190
216
 
191
- const toolParts = parts.filter((part) =>
192
- part.type.startsWith("tool-"),
193
- ) as ToolUIPart[];
217
+ const toolParts = parts.filter(isToolUIPart);
194
218
 
195
- // Guard against no tool parts
196
219
  if (toolParts.length === 0) {
197
220
  return false;
198
221
  }
199
222
 
200
- const allToolCallsCompleted = toolParts.every(
201
- (part) => part.state === "output-available",
223
+ const allToolCallsReady = toolParts.every((part) =>
224
+ isToolCallReadyToSend(part.state),
202
225
  );
203
226
 
204
227
  // Check if the last part has any text content
@@ -206,10 +229,9 @@ export function hasPendingToolCalls(messages: UIMessage[]): boolean {
206
229
  const hasTextContent =
207
230
  lastPart.type === "text" && lastPart.text?.trim().length > 0;
208
231
 
209
- Logger.warn("All tool calls completed: %s", allToolCallsCompleted);
232
+ Logger.debug("All tool calls ready to send: %s", allToolCallsReady);
210
233
 
211
- // Only auto-send if we have completed tool calls and there is no reply yet
212
- return allToolCallsCompleted && !hasTextContent;
234
+ return allToolCallsReady && !hasTextContent;
213
235
  }
214
236
 
215
237
  export function useFileState() {