@marimo-team/islands 0.15.2 → 0.15.3

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 (189) hide show
  1. package/dist/{ConnectedDataExplorerComponent-C39nQwtD.js → ConnectedDataExplorerComponent-DfvW3rBn.js} +323 -328
  2. package/dist/{ImageComparisonComponent-BhkiyswP.js → ImageComparisonComponent-XaJshw7d.js} +13 -13
  3. package/dist/{_baseUniq-DdHL34FO.js → _baseUniq-dN9WKF9m.js} +67 -67
  4. package/dist/any-language-editor-CpFniVi-.js +27 -0
  5. package/dist/{arc-BXrety1g.js → arc-BOhn-m2C.js} +1 -1
  6. package/dist/{architectureDiagram-KFL7JDKH-BMy6ywCF.js → architectureDiagram-W76B3OCA-Bpg85ZKv.js} +144 -144
  7. package/dist/assets/{worker-COGufAQn.js → worker-Y-Q4G-N2.js} +30 -26
  8. package/dist/asterisk-DS281yxp.js +271 -0
  9. package/dist/{blockDiagram-ZYB65J3Q-DYT2-nlI.js → blockDiagram-QIGZ2CNN-DS1kOHlW.js} +10 -10
  10. package/dist/{c4Diagram-AAMF2YG6-ZiQzioe6.js → c4Diagram-FPNF74CW-CyRVKssw.js} +8 -8
  11. package/dist/{channel-CeuXqUAU.js → channel-BilGXox7.js} +1 -1
  12. package/dist/{chunk-ANTBXLJU-BvYnIrdq.js → chunk-4BX2VUAB-CZR39zCO.js} +1 -1
  13. package/dist/{chunk-WVR4S24B-DXj8yaUk.js → chunk-55IACEB6-BIH-MYov.js} +1 -1
  14. package/dist/{chunk-GLLZNHP4-CyFsosAe.js → chunk-FMBD7UC4-4PZXFZE8.js} +1 -1
  15. package/dist/{chunk-JBRWN2VN-DA_EEhy2.js → chunk-K7UQS3LO-CEvWKznk.js} +117 -117
  16. package/dist/{chunk-NRVI72HA-BYx2jMlI.js → chunk-QN33PNHL-D5LO5Jq_.js} +1 -1
  17. package/dist/{chunk-FHKO5MBM-DfCztBk8.js → chunk-QZHKN3VN-6gwUonWI.js} +1 -1
  18. package/dist/{chunk-LXBSTHXV-Se7vdY6J.js → chunk-TVAH2DTR-3gm06QdU.js} +7 -7
  19. package/dist/{chunk-OMD6QJNC-CqgcPMgL.js → chunk-TZMSLE5B-Cm8Iy9bO.js} +1 -1
  20. package/dist/{classDiagram-v2-QTMF73CY-B19A3G1l.js → classDiagram-KNZD7YFC-DC529O_z.js} +2 -2
  21. package/dist/{classDiagram-3BZAVTQC-B19A3G1l.js → classDiagram-v2-RKCZMP56-DC529O_z.js} +2 -2
  22. package/dist/{clone-78au0tn1.js → clone-CLoRX3j6.js} +1 -1
  23. package/dist/cose-bilkent-S5V4N54A-qf5DlS6Y.js +2609 -0
  24. package/dist/{cytoscape.esm-BYnVVhJX.js → cytoscape.esm-DfdJODL8.js} +34 -34
  25. package/dist/{dagre-2BBEFEWP-BfEn3ZUV.js → dagre-5GWH7T2D-Ceocls0m.js} +6 -6
  26. package/dist/{data-grid-overlay-editor-CH_qLkV2.js → data-grid-overlay-editor-AqDS_UKe.js} +11 -11
  27. package/dist/{diagram-4IRLE6MV-CL8xidnG.js → diagram-N5W7TBWH-CP66oSiv.js} +59 -60
  28. package/dist/{diagram-RP2FKANI-B1BPcUew.js → diagram-QEK2KX5R-_YD4kxxi.js} +15 -15
  29. package/dist/{diagram-GUPCWM2R-CZ5cfqlq.js → diagram-S2PKOQOG-Cnj8T-OP.js} +10 -10
  30. package/dist/dockerfile-Cm8cRYCN.js +194 -0
  31. package/dist/ebnf-DUPDuY4r.js +78 -0
  32. package/dist/{erDiagram-HZWUO2LU-BEAIww50.js → erDiagram-AWTI2OKA-CGnvoHx6.js} +8 -8
  33. package/dist/fcl-CPC2WYrI.js +103 -0
  34. package/dist/{flowDiagram-THRYKUMA-Czs2UAI2.js → flowDiagram-PVAE7QVJ-DG-pr9R9.js} +9 -9
  35. package/dist/{ganttDiagram-WV7ZQ7D5-ByYIAVFO.js → ganttDiagram-OWAHRB6G-JmChtxvn.js} +34 -34
  36. package/dist/{gitGraphDiagram-OJR772UL-BcpDsiyB.js → gitGraphDiagram-NY62KEGX-D8wLfOPd.js} +4 -4
  37. package/dist/{glide-data-editor-CmN6FVyi.js → glide-data-editor-9nC3iCIZ.js} +33 -33
  38. package/dist/{graph-77W6heli.js → graph-CoRe7vAN.js} +3 -3
  39. package/dist/http-D9LttvKF.js +44 -0
  40. package/dist/{index-Bfk9dnyS.js → index-6qYeHHjQ.js} +33090 -32892
  41. package/dist/{index-BOojn38D.js → index-BpzLh4Qe.js} +7711 -7711
  42. package/dist/{index-CmozKMxx.js → index-BthgsgYX.js} +6 -6
  43. package/dist/{index-pBmAzQJl.js → index-MCx5v1x0.js} +2 -2
  44. package/dist/index-jkm77Jrz.js +98 -0
  45. package/dist/{infoDiagram-6WOFNB3A-CfzLHHVP.js → infoDiagram-STP46IZ2-BlXxvOrR.js} +2 -2
  46. package/dist/{journeyDiagram-FFXJYRFH-ndAcpkGn.js → journeyDiagram-BIP6EPQ6-CNRYs_Fc.js} +24 -26
  47. package/dist/{kanban-definition-KOZQBZVT-DcQYzNvc.js → kanban-definition-6OIFK2YF-B9HeMAuP.js} +14 -14
  48. package/dist/{layout-XySVHJgD.js → layout-m2vOUiW1.js} +81 -81
  49. package/dist/{linear-PbooOqg7.js → linear-DU6Q5CX3.js} +35 -35
  50. package/dist/{main-B5yML0bw.js → main-BD2KGFpU.js} +74594 -68034
  51. package/dist/main.js +1 -1
  52. package/dist/{mermaid-Cg5IX6Nv.js → mermaid-HVCtvbyx.js} +6160 -7493
  53. package/dist/min-DcGMA4e_.js +80 -0
  54. package/dist/mindmap-definition-Q6HEUPPD-BW8UmIDQ.js +785 -0
  55. package/dist/nginx-zDPm3Z74.js +89 -0
  56. package/dist/{number-overlay-editor-DUhfZqtP.js → number-overlay-editor-D8Hl0Syo.js} +19 -19
  57. package/dist/{pieDiagram-DBDJKBY4-DTOlNsja.js → pieDiagram-ADFJNKIX-Bg-3zg5U.js} +17 -17
  58. package/dist/{quadrantDiagram-YPSRARAO-BX2d8VS-.js → quadrantDiagram-LMRXKWRM-BO4IG6Yz.js} +6 -6
  59. package/dist/{react-plotly-Dcyw-3Sa.js → react-plotly-dkvHVuRb.js} +3577 -3577
  60. package/dist/{requirementDiagram-EGVEC5DT-D1T5u-wG.js → requirementDiagram-4UW4RH46-5sdTguSM.js} +7 -7
  61. package/dist/{sankeyDiagram-HRAUVNP4-G6xDfnp-.js → sankeyDiagram-GR3RE2ED-Buhlv9OI.js} +5 -5
  62. package/dist/sequenceDiagram-C3RYC4MD-C3qsM2UP.js +2519 -0
  63. package/dist/{slides-component-BJLlPJSr.js → slides-component-D209A0-s.js} +66 -66
  64. package/dist/solr-BNlsLglM.js +41 -0
  65. package/dist/spreadsheet-C-cy4P5N.js +49 -0
  66. package/dist/{stateDiagram-UUKSUZ4H-CYXbjaom.js → stateDiagram-KXAO66HF-CopJ7G6P.js} +5 -5
  67. package/dist/{stateDiagram-v2-EYPG3UTE-Br1HYKT6.js → stateDiagram-v2-UMBNRL4Z-CejL8AKf.js} +2 -2
  68. package/dist/style.css +1 -1
  69. package/dist/tiddlywiki-5wqsXtSk.js +155 -0
  70. package/dist/tiki-__Kn3CeS.js +181 -0
  71. package/dist/{time-B9SZnSen.js → time-BwSBitlN.js} +58 -58
  72. package/dist/{timeline-definition-3HZDQTIS-DeK_ZRD0.js → timeline-definition-XQNQX7LJ-DzMNTX-C.js} +10 -12
  73. package/dist/{timer-BYwnU4DF.js → timer-B0-z63CM.js} +16 -16
  74. package/dist/{treemap-75Q7IDZK-CKP4vV_0.js → treemap-75Q7IDZK-zeJG07dk.js} +14 -14
  75. package/dist/{vega-component-CpgdqX2d.js → vega-component-CUkiTayd.js} +30 -30
  76. package/dist/{xychartDiagram-FDP5SA34-AMEPsx_R.js → xychartDiagram-6GGTOJPD-DiENNXMS.js} +7 -7
  77. package/package.json +39 -39
  78. package/src/__mocks__/notebook.ts +3 -0
  79. package/src/__mocks__/requests.ts +3 -0
  80. package/src/__tests__/__snapshots__/CellStatus.test.tsx.snap +12 -12
  81. package/src/__tests__/chat-utils.test.ts +26 -211
  82. package/src/components/ai/ai-model-dropdown.tsx +25 -9
  83. package/src/components/app-config/ai-config.tsx +7 -0
  84. package/src/components/chat/chat-components.tsx +71 -0
  85. package/src/components/chat/chat-panel.tsx +481 -291
  86. package/src/components/chat/chat-utils.ts +50 -0
  87. package/src/components/chat/markdown-renderer.tsx +3 -7
  88. package/src/components/chat/tool-call-accordion.tsx +5 -5
  89. package/src/components/datasources/__tests__/utils.test.ts +6 -0
  90. package/src/components/datasources/column-preview.tsx +1 -3
  91. package/src/components/editor/actions/useNotebookActions.tsx +1 -1
  92. package/src/components/editor/ai/add-cell-with-ai.tsx +20 -15
  93. package/src/components/editor/ai/ai-completion-editor.tsx +22 -3
  94. package/src/components/editor/ai/completion-handlers.tsx +2 -4
  95. package/src/components/editor/ai/completion-utils.ts +85 -11
  96. package/src/components/editor/alerts/startup-logs-alert.tsx +72 -0
  97. package/src/components/editor/chrome/panels/datasources-panel.tsx +3 -1
  98. package/src/components/editor/chrome/panels/dependency-graph-panel.tsx +3 -1
  99. package/src/components/editor/chrome/panels/documentation-panel.tsx +3 -1
  100. package/src/components/editor/chrome/panels/error-panel.tsx +3 -1
  101. package/src/components/editor/chrome/panels/file-explorer-panel.tsx +3 -1
  102. package/src/components/editor/chrome/panels/logs-panel.tsx +3 -1
  103. package/src/components/editor/chrome/panels/outline-panel.tsx +3 -1
  104. package/src/components/editor/chrome/panels/packages-panel.tsx +4 -2
  105. package/src/components/editor/chrome/panels/scratchpad-panel.tsx +3 -1
  106. package/src/components/editor/chrome/panels/secrets-panel.tsx +3 -1
  107. package/src/components/editor/chrome/panels/snippets-panel.tsx +3 -1
  108. package/src/components/editor/chrome/panels/tracing-panel.tsx +3 -1
  109. package/src/components/editor/chrome/panels/variable-panel.tsx +3 -1
  110. package/src/components/editor/chrome/wrapper/app-chrome.tsx +38 -28
  111. package/src/components/editor/controls/command-palette-button.tsx +1 -1
  112. package/src/components/editor/controls/command-palette.tsx +5 -4
  113. package/src/components/editor/controls/state.ts +4 -0
  114. package/src/components/editor/package-alert.tsx +108 -58
  115. package/src/components/editor/renderers/CellArray.tsx +2 -0
  116. package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +0 -1
  117. package/src/components/pages/edit-page.tsx +7 -3
  118. package/src/core/ai/chat-utils.ts +26 -43
  119. package/src/core/ai/config.ts +1 -1
  120. package/src/core/ai/context/__tests__/registry.test.ts +277 -3
  121. package/src/core/ai/context/context.ts +11 -1
  122. package/src/core/ai/context/providers/__tests__/cell-output.test.ts +378 -0
  123. package/src/core/ai/context/providers/__tests__/error.test.ts +3 -2
  124. package/src/core/ai/context/providers/__tests__/file.test.ts +119 -0
  125. package/src/core/ai/context/providers/cell-output.ts +349 -0
  126. package/src/core/ai/context/providers/common.ts +5 -1
  127. package/src/core/ai/context/providers/file.ts +287 -0
  128. package/src/core/ai/context/registry.ts +79 -0
  129. package/src/core/ai/state.ts +22 -5
  130. package/src/core/alerts/state.ts +71 -3
  131. package/src/core/cells/cell.ts +2 -2
  132. package/src/core/cells/cells.ts +1 -1
  133. package/src/core/cells/logs.ts +1 -1
  134. package/src/core/cells/runs.ts +6 -5
  135. package/src/core/codemirror/ai/resources.ts +47 -5
  136. package/src/core/codemirror/ai/state.ts +12 -0
  137. package/src/core/codemirror/language/__tests__/sql.test.ts +45 -0
  138. package/src/core/codemirror/markdown/__tests__/commands.test.ts +1 -0
  139. package/src/core/codemirror/theme/dark.ts +1 -1
  140. package/src/core/config/capabilities.ts +1 -1
  141. package/src/core/datasets/__tests__/data-source.test.ts +24 -0
  142. package/src/core/errors/__tests__/errors.test.ts +2 -0
  143. package/src/core/islands/bridge.ts +1 -0
  144. package/src/core/islands/main.ts +1 -0
  145. package/src/core/kernel/messages.ts +12 -6
  146. package/src/core/layout/layout.ts +3 -3
  147. package/src/core/network/requests-network.ts +8 -0
  148. package/src/core/network/requests-static.ts +1 -0
  149. package/src/core/network/requests-toasting.ts +1 -0
  150. package/src/core/network/types.ts +4 -1
  151. package/src/core/wasm/bridge.ts +18 -2
  152. package/src/core/wasm/worker/bootstrap.ts +3 -1
  153. package/src/core/wasm/worker/getMarimoWheel.ts +3 -8
  154. package/src/core/wasm/worker/types.ts +3 -0
  155. package/src/core/websocket/useMarimoWebSocket.tsx +7 -1
  156. package/src/css/app/Cell.css +42 -21
  157. package/src/css/app/codemirror.css +5 -1
  158. package/src/css/globals.css +3 -0
  159. package/src/css/md.css +1 -1
  160. package/src/plugins/impl/MicrophonePlugin.tsx +2 -2
  161. package/src/plugins/impl/chat/ChatPlugin.tsx +2 -9
  162. package/src/plugins/impl/chat/chat-ui.tsx +129 -110
  163. package/src/plugins/impl/chat/types.ts +5 -8
  164. package/src/plugins/impl/code/__tests__/language.test.ts +15 -0
  165. package/src/plugins/impl/code/any-language-editor.tsx +11 -8
  166. package/src/plugins/layout/MimeRenderPlugin.tsx +3 -6
  167. package/src/stories/cell.stories.tsx +6 -0
  168. package/src/stories/layout/vertical/one-column.stories.tsx +215 -0
  169. package/src/theme/useTheme.ts +11 -6
  170. package/src/utils/__tests__/blob.test.ts +37 -0
  171. package/src/utils/arrays.ts +13 -0
  172. package/src/utils/fileToBase64.ts +21 -6
  173. package/src/utils/json/base64.ts +5 -2
  174. package/src/utils/numbers.ts +9 -7
  175. package/dist/any-language-editor-DC5170DQ.js +0 -45
  176. package/dist/asn1-jKiBa2Ya.js +0 -95
  177. package/dist/clojure-CCKyeQKf.js +0 -800
  178. package/dist/css-BkF-NPzE.js +0 -1553
  179. package/dist/index-5ZH_qS8j.js +0 -288
  180. package/dist/index-U4yn89qO.js +0 -341
  181. package/dist/javascript-C2yteZeJ.js +0 -691
  182. package/dist/min-DS5Jz-hg.js +0 -80
  183. package/dist/mindmap-definition-LNHGMQRG-0aOVaMR8.js +0 -3234
  184. package/dist/mllike-BSnXJBGA.js +0 -272
  185. package/dist/pug-CwAQJzGR.js +0 -248
  186. package/dist/python-BkR3uSy8.js +0 -313
  187. package/dist/rpm-IznJm2Xc.js +0 -57
  188. package/dist/sequenceDiagram-WFGC7UMF-DMhHzllb.js +0 -2284
  189. package/dist/ttcn-cfg-Bac_acMi.js +0 -88
@@ -1,8 +1,9 @@
1
1
  /* Copyright 2024 Marimo. All rights reserved. */
2
2
 
3
3
  import type { Completion } from "@codemirror/autocomplete";
4
+ import type { FileUIPart } from "ai";
4
5
  import { createStore } from "jotai";
5
- import { beforeEach, describe, expect, it } from "vitest";
6
+ import { beforeEach, describe, expect, it, vi } from "vitest";
6
7
  import { MockNotebook } from "@/__mocks__/notebook";
7
8
  import { notebookAtom } from "@/core/cells/cells";
8
9
  import { CellId as CellIdClass } from "@/core/cells/ids";
@@ -19,10 +20,28 @@ import {
19
20
 
20
21
  // Mock context item for testing
21
22
  interface MockContextItem extends AIContextItem {
22
- type: "mock";
23
- data: { value: string };
23
+ type: "mock" | "attachment";
24
+ data: {
25
+ value: string;
26
+ needsAttachment?: boolean;
27
+ };
24
28
  }
25
29
 
30
+ // Mock attachment data for testing
31
+ const mockAttachment1: FileUIPart = {
32
+ type: "file",
33
+ filename: "test-image-1.png",
34
+ mediaType: "image/png",
35
+ url: "data:image/png;base64,mockdata1",
36
+ };
37
+
38
+ const mockAttachment2: FileUIPart = {
39
+ type: "file",
40
+ filename: "test-image-2.jpg",
41
+ mediaType: "image/jpeg",
42
+ url: "data:image/jpeg;base64,mockdata2",
43
+ };
44
+
26
45
  // Concrete implementation of AIContextProvider for testing
27
46
  class MockContextProvider extends AIContextProvider<MockContextItem> {
28
47
  readonly title = "Mock Items";
@@ -76,6 +95,44 @@ class MockContextProvider extends AIContextProvider<MockContextItem> {
76
95
  }
77
96
  }
78
97
 
98
+ // Test provider that supports attachments
99
+ class AttachmentContextProvider extends AIContextProvider<MockContextItem> {
100
+ readonly title = "Attachment Items";
101
+ readonly mentionPrefix = "@";
102
+ readonly contextType = "attachment";
103
+
104
+ constructor(
105
+ private items: MockContextItem[] = [],
106
+ private attachments: FileUIPart[] = [],
107
+ ) {
108
+ super();
109
+ }
110
+
111
+ getItems(): MockContextItem[] {
112
+ return this.items;
113
+ }
114
+
115
+ formatContext(item: MockContextItem): string {
116
+ return `Attachment: ${item.name} (${item.data.value})`;
117
+ }
118
+
119
+ formatCompletion(item: MockContextItem): Completion {
120
+ return this.createBasicCompletion(item);
121
+ }
122
+
123
+ override async getAttachments(
124
+ items: MockContextItem[],
125
+ ): Promise<FileUIPart[]> {
126
+ // Return attachments for items that need them
127
+ const itemsNeedingAttachments = items.filter(
128
+ (item) => item.data.needsAttachment,
129
+ );
130
+ return itemsNeedingAttachments
131
+ .map((_, index) => this.attachments[index % this.attachments.length])
132
+ .filter(Boolean);
133
+ }
134
+ }
135
+
79
136
  interface FileContextItem extends AIContextItem {
80
137
  type: "file";
81
138
  data: { value: string };
@@ -667,4 +724,221 @@ describe("AIContextRegistry", () => {
667
724
  expect(formattedContext).toContain("Item 1");
668
725
  });
669
726
  });
727
+
728
+ describe("attachment functionality", () => {
729
+ let attachmentProvider: AttachmentContextProvider;
730
+ let attachmentRegistry: AIContextRegistry<MockContextItem>;
731
+
732
+ beforeEach(() => {
733
+ const itemWithAttachment: MockContextItem = {
734
+ uri: "attachment://item1" as ContextLocatorId,
735
+ name: "item1",
736
+ type: "attachment",
737
+ description: "Item with attachment",
738
+ data: { needsAttachment: true, value: "test1" },
739
+ };
740
+
741
+ const itemWithoutAttachment: MockContextItem = {
742
+ uri: "attachment://item2" as ContextLocatorId,
743
+ name: "item2",
744
+ type: "attachment",
745
+ description: "Item without attachment",
746
+ data: { needsAttachment: false, value: "test2" },
747
+ };
748
+
749
+ attachmentProvider = new AttachmentContextProvider(
750
+ [itemWithAttachment, itemWithoutAttachment],
751
+ [mockAttachment1, mockAttachment2],
752
+ );
753
+
754
+ attachmentRegistry = new AIContextRegistry<MockContextItem>().register(
755
+ attachmentProvider,
756
+ );
757
+ });
758
+
759
+ describe("getAttachmentsForContext", () => {
760
+ it("should return empty array for empty context IDs", async () => {
761
+ const attachments = await attachmentRegistry.getAttachmentsForContext(
762
+ [],
763
+ );
764
+ expect(attachments).toEqual([]);
765
+ });
766
+
767
+ it("should return empty array for non-existent context IDs", async () => {
768
+ const nonExistentIds = ["attachment://nonexistent" as ContextLocatorId];
769
+ const attachments =
770
+ await attachmentRegistry.getAttachmentsForContext(nonExistentIds);
771
+ expect(attachments).toEqual([]);
772
+ });
773
+
774
+ it("should get attachments from provider that supports them", async () => {
775
+ const contextIds = ["attachment://item1" as ContextLocatorId];
776
+ const attachments =
777
+ await attachmentRegistry.getAttachmentsForContext(contextIds);
778
+
779
+ expect(attachments).toHaveLength(1);
780
+ expect(attachments[0]).toEqual(mockAttachment1);
781
+ });
782
+
783
+ it("should not get attachments for items that don't need them", async () => {
784
+ const contextIds = ["attachment://item2" as ContextLocatorId];
785
+ const attachments =
786
+ await attachmentRegistry.getAttachmentsForContext(contextIds);
787
+
788
+ expect(attachments).toHaveLength(0);
789
+ });
790
+
791
+ it("should not get attachments from providers with default implementation", async () => {
792
+ const simpleRegistry =
793
+ new AIContextRegistry<MockContextItem>().register(mockProvider);
794
+
795
+ const contextIds = ["mock://item1" as ContextLocatorId];
796
+ const attachments =
797
+ await simpleRegistry.getAttachmentsForContext(contextIds);
798
+
799
+ expect(attachments).toHaveLength(0);
800
+ });
801
+
802
+ it("should handle multiple context IDs from different providers", async () => {
803
+ const mixedRegistry = new AIContextRegistry<MockContextItem>()
804
+ .register(attachmentProvider)
805
+ .register(mockProvider);
806
+
807
+ const contextIds = [
808
+ "attachment://item1" as ContextLocatorId, // should have attachment
809
+ "attachment://item2" as ContextLocatorId, // should not have attachment
810
+ "mock://item1" as ContextLocatorId, // provider doesn't support attachments
811
+ ];
812
+
813
+ const attachments =
814
+ await mixedRegistry.getAttachmentsForContext(contextIds);
815
+
816
+ // Only attachment://item1 should contribute an attachment
817
+ expect(attachments).toHaveLength(1);
818
+ expect(attachments[0]).toEqual(mockAttachment1);
819
+ });
820
+
821
+ it("should handle multiple items needing attachments", async () => {
822
+ const multiItem1: MockContextItem = {
823
+ uri: "attachment://multi1" as ContextLocatorId,
824
+ name: "multi1",
825
+ type: "attachment",
826
+ description: "Multi item 1",
827
+ data: { needsAttachment: true, value: "multi1" },
828
+ };
829
+
830
+ const multiItem2: MockContextItem = {
831
+ uri: "attachment://multi2" as ContextLocatorId,
832
+ name: "multi2",
833
+ type: "attachment",
834
+ description: "Multi item 2",
835
+ data: { needsAttachment: true, value: "multi2" },
836
+ };
837
+
838
+ const multiProvider = new AttachmentContextProvider(
839
+ [multiItem1, multiItem2],
840
+ [mockAttachment1, mockAttachment2],
841
+ );
842
+
843
+ const multiRegistry = new AIContextRegistry<MockContextItem>().register(
844
+ multiProvider,
845
+ );
846
+
847
+ const contextIds = [
848
+ "attachment://multi1" as ContextLocatorId,
849
+ "attachment://multi2" as ContextLocatorId,
850
+ ];
851
+
852
+ const attachments =
853
+ await multiRegistry.getAttachmentsForContext(contextIds);
854
+
855
+ expect(attachments).toHaveLength(2);
856
+ expect(attachments).toContainEqual(mockAttachment1);
857
+ expect(attachments).toContainEqual(mockAttachment2);
858
+ });
859
+
860
+ it("should handle provider errors gracefully", async () => {
861
+ const errorProvider = new AttachmentContextProvider([
862
+ {
863
+ uri: "attachment://error-item" as ContextLocatorId,
864
+ name: "error-item",
865
+ type: "attachment",
866
+ description: "Item that causes error",
867
+ data: { needsAttachment: true, value: "error" },
868
+ },
869
+ ]);
870
+
871
+ // Override getAttachments to throw an error
872
+ errorProvider.getAttachments = vi
873
+ .fn()
874
+ .mockRejectedValue(new Error("Attachment error"));
875
+
876
+ const errorRegistry = new AIContextRegistry<MockContextItem>()
877
+ .register(errorProvider)
878
+ .register(mockProvider);
879
+
880
+ const contextIds = [
881
+ "attachment://error-item" as ContextLocatorId,
882
+ "mock://item1" as ContextLocatorId,
883
+ ];
884
+
885
+ // Should not throw and should handle the error gracefully
886
+ const attachments =
887
+ await errorRegistry.getAttachmentsForContext(contextIds);
888
+ expect(attachments).toEqual([]);
889
+ });
890
+ });
891
+
892
+ describe("provider batching for attachments", () => {
893
+ it("should batch multiple items to the same provider", async () => {
894
+ const getAttachmentsSpy = vi.spyOn(
895
+ attachmentProvider,
896
+ "getAttachments",
897
+ );
898
+
899
+ const contextIds = [
900
+ "attachment://item1" as ContextLocatorId,
901
+ "attachment://item2" as ContextLocatorId,
902
+ ];
903
+
904
+ await attachmentRegistry.getAttachmentsForContext(contextIds);
905
+
906
+ // Should call getAttachments once with both items
907
+ expect(getAttachmentsSpy).toHaveBeenCalledTimes(1);
908
+ expect(getAttachmentsSpy).toHaveBeenCalledWith(
909
+ expect.arrayContaining([
910
+ expect.objectContaining({ uri: "attachment://item1" }),
911
+ expect.objectContaining({ uri: "attachment://item2" }),
912
+ ]),
913
+ );
914
+ });
915
+
916
+ it("should call different providers separately", async () => {
917
+ const mixedRegistry = new AIContextRegistry<MockContextItem>()
918
+ .register(attachmentProvider)
919
+ .register(mockProvider);
920
+
921
+ const attachmentSpy = vi.spyOn(attachmentProvider, "getAttachments");
922
+ const mockSpy = vi.spyOn(mockProvider, "getAttachments");
923
+
924
+ const contextIds = [
925
+ "attachment://item1" as ContextLocatorId,
926
+ "mock://item1" as ContextLocatorId,
927
+ ];
928
+
929
+ await mixedRegistry.getAttachmentsForContext(contextIds);
930
+
931
+ // Should call each provider once
932
+ expect(attachmentSpy).toHaveBeenCalledTimes(1);
933
+ expect(mockSpy).toHaveBeenCalledTimes(1);
934
+
935
+ expect(attachmentSpy).toHaveBeenCalledWith([
936
+ expect.objectContaining({ uri: "attachment://item1" }),
937
+ ]);
938
+ expect(mockSpy).toHaveBeenCalledWith([
939
+ expect.objectContaining({ uri: "mock://item1" }),
940
+ ]);
941
+ });
942
+ });
943
+ });
670
944
  });
@@ -1,9 +1,12 @@
1
1
  /* Copyright 2024 Marimo. All rights reserved. */
2
2
 
3
3
  import { allTablesAtom } from "@/core/datasets/data-source-connections";
4
+ import { getRequestClient } from "@/core/network/requests";
4
5
  import type { JotaiStore } from "@/core/state/jotai";
5
6
  import { variablesAtom } from "@/core/variables/state";
7
+ import { CellOutputContextProvider } from "./providers/cell-output";
6
8
  import { ErrorContextProvider } from "./providers/error";
9
+ import { FileContextProvider } from "./providers/file";
7
10
  import { TableContextProvider } from "./providers/tables";
8
11
  import { VariableContextProvider } from "./providers/variable";
9
12
  import { AIContextRegistry } from "./registry";
@@ -11,8 +14,15 @@ import { AIContextRegistry } from "./registry";
11
14
  export function getAIContextRegistry(store: JotaiStore) {
12
15
  const tablesMap = store.get(allTablesAtom);
13
16
  const variables = store.get(variablesAtom);
17
+
14
18
  return new AIContextRegistry()
15
19
  .register(new TableContextProvider(tablesMap))
16
20
  .register(new VariableContextProvider(variables, tablesMap))
17
- .register(new ErrorContextProvider(store));
21
+ .register(new ErrorContextProvider(store))
22
+ .register(new CellOutputContextProvider(store));
23
+ }
24
+
25
+ export function getFileContextProvider(): FileContextProvider {
26
+ const apiRequests = getRequestClient();
27
+ return new FileContextProvider(apiRequests);
18
28
  }