@marimo-team/islands 0.23.14-dev19 → 0.23.14-dev21

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.
@@ -1,6 +1,6 @@
1
1
  import { s as __toESM } from "./chunk-BNovOVIE.js";
2
2
  import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
3
- import { at as parseHtmlContent, it as ansiToPlainText } from "./html-to-image-DBHfshED.js";
3
+ import { at as parseHtmlContent, it as ansiToPlainText } from "./html-to-image-DLSOS-bE.js";
4
4
  import { u as createLucideIcon } from "./dist--2Bqjvs0.js";
5
5
  import { t as Strings } from "./strings-Dq_j3Rxw.js";
6
6
  import { t as require_jsx_runtime } from "./jsx-runtime-DebpN0FN.js";
@@ -6,10 +6,10 @@ import { s as __toESM } from "./chunk-BNovOVIE.js";
6
6
  import { _ as Logger, g as cn, h as Events, l as useEventListener, t as Button } from "./button-BacYv-bE.js";
7
7
  import { t as require_react } from "./react-DA-nE2FX.js";
8
8
  import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
9
- import { lt as kioskModeAtom } from "./html-to-image-DBHfshED.js";
9
+ import { lt as kioskModeAtom } from "./html-to-image-DLSOS-bE.js";
10
10
  import "./chunk-5FQGJX7Z-BbqSm5gU.js";
11
11
  import { u as createLucideIcon } from "./dist--2Bqjvs0.js";
12
- import { a as DEFAULT_SLIDE_TYPE, c as Slide, ct as PanelGroup, i as DEFAULT_DECK_TRANSITION, lt as PanelResizeHandle, on as EyeOff, s as SlideSidebar, sn as Expand, st as Panel, t as useNotebookCodeAvailable, un as Code } from "./code-visibility-CxDvzm2S.js";
12
+ import { a as DEFAULT_SLIDE_TYPE, c as Slide, ct as PanelGroup, i as DEFAULT_DECK_TRANSITION, lt as PanelResizeHandle, on as EyeOff, s as SlideSidebar, sn as Expand, st as Panel, t as useNotebookCodeAvailable, un as Code } from "./code-visibility-CPWEmIAX.js";
13
13
  import { X as useDebouncedCallback } from "./input-BSdZp5Ng.js";
14
14
  import "./toDate-D1Z7ZXWh.js";
15
15
  import "./react-dom-BTJzcVJ9.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.23.14-dev19",
3
+ "version": "0.23.14-dev21",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -56,7 +56,7 @@
56
56
  "@lezer/python": "^1.1.18",
57
57
  "@marimo-team/codemirror-ai": "^0.3.7",
58
58
  "@marimo-team/codemirror-languageserver": "^1.16.12",
59
- "@marimo-team/codemirror-mcp": "^0.1.5",
59
+ "@marimo-team/codemirror-mcp": "^0.1.7",
60
60
  "@marimo-team/codemirror-sql": "^0.2.8",
61
61
  "@marimo-team/llm-info": "workspace:*",
62
62
  "@marimo-team/marimo-api": "workspace:*",
@@ -1,119 +1,23 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
- import { beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { describe, expect, it } from "vitest";
4
4
  import { cellId } from "@/__tests__/branded";
5
- import type { MarimoError } from "@/core/kernel/messages";
5
+ import { buildFixInChatPrompt } from "../auto-fix";
6
6
 
7
- const getDatasourceContext = vi.fn<(id: unknown) => string | null>(() => null);
8
-
9
- vi.mock("@/core/ai/context/providers/datasource", () => ({
10
- getDatasourceContext: (id: unknown) => getDatasourceContext(id),
11
- }));
12
-
13
- const { buildFixPrompt, buildFixPromptFromText } = await import("../auto-fix");
14
-
15
- describe("buildFixPromptFromText", () => {
16
- beforeEach(() => {
17
- getDatasourceContext.mockReset();
18
- getDatasourceContext.mockReturnValue(null);
19
- });
20
-
21
- it("includes the cell id in the header when provided", () => {
22
- const prompt = buildFixPromptFromText("boom", cellId("cell-1"));
23
- expect(prompt).toBe(
24
- "My cell (id: cell-1) produced the following error. Please fix it:\n\nboom",
25
- );
26
- });
27
-
28
- it("uses a generic header when no cell id is provided", () => {
29
- const prompt = buildFixPromptFromText("boom");
30
- expect(prompt).toBe(
31
- "My code gives the following error. Please fix it:\n\nboom",
7
+ describe("buildFixInChatPrompt", () => {
8
+ it("references the per-cell error context URI", () => {
9
+ expect(buildFixInChatPrompt(cellId("cell-1"))).toBe(
10
+ "@error://cell-1\n\nPlease fix this error.",
32
11
  );
33
12
  });
34
13
 
35
- it("appends datasource context when explicitly requested for the cell", () => {
36
- getDatasourceContext.mockReturnValue("@datasource://my_db");
37
- const prompt = buildFixPromptFromText("boom", cellId("cell-1"), {
38
- includeDatasourceContext: true,
39
- });
40
- expect(prompt).toBe(
41
- "My cell (id: cell-1) produced the following error. Please fix it:\n\nboom\n\nDatabase schema: @datasource://my_db",
14
+ it("falls back to inline error text when no cell id", () => {
15
+ expect(buildFixInChatPrompt(undefined, "ValueError: boom")).toBe(
16
+ "My code gives the following error. Please fix it:\n\nValueError: boom",
42
17
  );
43
18
  });
44
19
 
45
- it("does not append datasource context by default", () => {
46
- getDatasourceContext.mockReturnValue("@datasource://my_db");
47
- const prompt = buildFixPromptFromText("boom", cellId("cell-1"));
48
- expect(prompt).toBe(
49
- "My cell (id: cell-1) produced the following error. Please fix it:\n\nboom",
50
- );
51
- expect(getDatasourceContext).not.toHaveBeenCalled();
52
- });
53
-
54
- it("does not look up datasource context without a cell id", () => {
55
- buildFixPromptFromText("boom", undefined, {
56
- includeDatasourceContext: true,
57
- });
58
- expect(getDatasourceContext).not.toHaveBeenCalled();
59
- });
60
- });
61
-
62
- describe("buildFixPrompt", () => {
63
- beforeEach(() => {
64
- getDatasourceContext.mockReset();
65
- getDatasourceContext.mockReturnValue(null);
66
- });
67
-
68
- it("uses the error message when present", () => {
69
- const errors: MarimoError[] = [
70
- { type: "sql-error", msg: "syntax error", sql_statement: "SELECT" },
71
- ];
72
- expect(buildFixPrompt(errors, cellId("cell-1"))).toBe(
73
- "My cell (id: cell-1) produced the following error. Please fix it:\n\nsyntax error",
74
- );
75
- });
76
-
77
- it("joins multiple errors with newlines", () => {
78
- const errors: MarimoError[] = [
79
- { type: "syntax", msg: "bad syntax" },
80
- {
81
- type: "exception",
82
- exception_type: "ValueError",
83
- msg: "bad value",
84
- raising_cell: null,
85
- },
86
- ];
87
- expect(buildFixPrompt(errors, cellId("cell-1"))).toBe(
88
- "My cell (id: cell-1) produced the following error. Please fix it:\n\nbad syntax\nbad value",
89
- );
90
- });
91
-
92
- it("falls back to the error type when there is no message", () => {
93
- const errors: MarimoError[] = [
94
- { type: "multiple-defs", name: "foo", cells: [cellId("foo")] },
95
- ];
96
- expect(buildFixPrompt(errors, cellId("cell-1"))).toBe(
97
- "My cell (id: cell-1) produced the following error. Please fix it:\n\nmultiple-defs",
98
- );
99
- });
100
-
101
- it("appends datasource context for SQL errors", () => {
102
- getDatasourceContext.mockReturnValue("@datasource://my_db");
103
- const errors: MarimoError[] = [
104
- { type: "sql-error", msg: "syntax error", sql_statement: "SELECT" },
105
- ];
106
- expect(buildFixPrompt(errors, cellId("cell-1"))).toBe(
107
- "My cell (id: cell-1) produced the following error. Please fix it:\n\nsyntax error\n\nDatabase schema: @datasource://my_db",
108
- );
109
- });
110
-
111
- it("does not append datasource context for non-SQL errors", () => {
112
- getDatasourceContext.mockReturnValue("@datasource://my_db");
113
- const errors: MarimoError[] = [{ type: "syntax", msg: "bad syntax" }];
114
- expect(buildFixPrompt(errors, cellId("cell-1"))).toBe(
115
- "My cell (id: cell-1) produced the following error. Please fix it:\n\nbad syntax",
116
- );
117
- expect(getDatasourceContext).not.toHaveBeenCalled();
20
+ it("uses a generic prompt when no cell id and no error text", () => {
21
+ expect(buildFixInChatPrompt(undefined)).toBe("Please fix this error.");
118
22
  });
119
23
  });
@@ -21,44 +21,24 @@ import { aiCompletionCellAtom } from "@/core/ai/state";
21
21
  import { notebookAtom, useCellActions } from "@/core/cells/cells";
22
22
  import type { CellId } from "@/core/cells/ids";
23
23
  import { aiFeaturesEnabledAtom } from "@/core/config/config";
24
- import { getDatasourceContext } from "@/core/ai/context/providers/datasource";
25
24
  import { getAutoFixes } from "@/core/errors/errors";
26
25
  import type { MarimoError } from "@/core/kernel/messages";
27
26
  import { cn } from "@/utils/cn";
28
27
  import { useOpenAiAssistant } from "../chrome/wrapper/useOpenAiAssistant";
29
28
  import { type FixMode, useFixMode } from "./fix-mode";
30
29
 
31
- export function buildFixPromptFromText(
32
- errorText: string,
33
- cellId?: CellId,
34
- {
35
- includeDatasourceContext = false,
36
- }: { includeDatasourceContext?: boolean } = {},
30
+ export function buildFixInChatPrompt(
31
+ cellId: CellId | undefined,
32
+ fallbackErrorText?: string,
37
33
  ): string {
38
- const header =
39
- cellId != null
40
- ? `My cell (id: ${cellId}) produced the following error. Please fix it:`
41
- : "My code gives the following error. Please fix it:";
42
- let prompt = `${header}\n\n${errorText}`;
43
- if (cellId != null && includeDatasourceContext) {
44
- const datasourceContext = getDatasourceContext(cellId);
45
- if (datasourceContext) {
46
- prompt += `\n\nDatabase schema: ${datasourceContext}`;
47
- }
34
+ if (cellId != null) {
35
+ return `@error://${cellId}\n\nPlease fix this error.`;
48
36
  }
49
- return prompt;
50
- }
51
-
52
- export function buildFixPrompt(errors: MarimoError[], cellId: CellId): string {
53
- const errorText = errors
54
- .map((error) => ("msg" in error && error.msg ? error.msg : error.type))
55
- .join("\n");
56
- const includeDatasourceContext = errors.some(
57
- (error) => error.type === "sql-error",
58
- );
59
- return buildFixPromptFromText(errorText, cellId, {
60
- includeDatasourceContext,
61
- });
37
+ const errorText = fallbackErrorText?.trim();
38
+ if (errorText) {
39
+ return `My code gives the following error. Please fix it:\n\n${errorText}`;
40
+ }
41
+ return "Please fix this error.";
62
42
  }
63
43
 
64
44
  export const AutoFixButton = ({
@@ -112,7 +92,7 @@ export const AutoFixButton = ({
112
92
 
113
93
  const openAISidebar = () => {
114
94
  openAiAssistant({
115
- prompt: buildFixPrompt(errors, cellId),
95
+ prompt: buildFixInChatPrompt(cellId),
116
96
  });
117
97
  };
118
98
 
@@ -42,7 +42,7 @@ import {
42
42
  getTracebackInfo,
43
43
  } from "@/utils/traceback";
44
44
  import { useOpenAiAssistant } from "../chrome/wrapper/useOpenAiAssistant";
45
- import { AIFixButton, buildFixPromptFromText } from "../errors/auto-fix";
45
+ import { AIFixButton, buildFixInChatPrompt } from "../errors/auto-fix";
46
46
  import { MangledSegments } from "../errors/mangled-local-chip";
47
47
  import { CellLinkTraceback } from "../links/cell-link";
48
48
  import type { OnRefactorWithAI } from "../Output";
@@ -101,7 +101,7 @@ export const MarimoTracebackOutput = ({
101
101
 
102
102
  const openAISidebar = () => {
103
103
  openAiAssistant({
104
- prompt: buildFixPromptFromText(lastTracebackLine, cellId),
104
+ prompt: buildFixInChatPrompt(cellId, lastTracebackLine),
105
105
  });
106
106
  };
107
107
 
@@ -176,6 +176,33 @@ class FileContextProvider extends AIContextProvider<FileContextItem> {
176
176
  }
177
177
  }
178
178
 
179
+ // Second mock provider with the same context type as MockContextProvider
180
+ class SecondaryMockProvider extends AIContextProvider<MockContextItem> {
181
+ readonly title = "Secondary Mock Items";
182
+ readonly mentionPrefix = "@";
183
+ readonly contextType = "mock";
184
+
185
+ getItems(): MockContextItem[] {
186
+ return [
187
+ {
188
+ type: "mock",
189
+ uri: this.asURI("secondary-item"),
190
+ name: "Secondary Item",
191
+ description: "From the second mock provider",
192
+ data: { value: "secondary" },
193
+ },
194
+ ];
195
+ }
196
+
197
+ formatContext(item: MockContextItem): string {
198
+ return `Secondary: ${item.name}`;
199
+ }
200
+
201
+ formatCompletion(item: MockContextItem): Completion {
202
+ return this.createBasicCompletion(item);
203
+ }
204
+ }
205
+
179
206
  describe("AIContextProvider", () => {
180
207
  let provider: MockContextProvider;
181
208
 
@@ -411,6 +438,95 @@ describe("AIContextRegistry", () => {
411
438
  });
412
439
  });
413
440
 
441
+ describe("resolveItems", () => {
442
+ let mockGetItems: ReturnType<typeof vi.spyOn>;
443
+ let fileGetItems: ReturnType<typeof vi.spyOn>;
444
+
445
+ beforeEach(() => {
446
+ registry.register(mockProvider);
447
+ registry.register(fileProvider);
448
+ mockGetItems = vi.spyOn(mockProvider, "getItems");
449
+ fileGetItems = vi.spyOn(fileProvider, "getItems");
450
+ });
451
+
452
+ it("should resolve valid IDs from the matching providers only", () => {
453
+ const resolved = registry.resolveItems([
454
+ "mock://item1",
455
+ "file://config.py",
456
+ ] as ContextLocatorId[]);
457
+
458
+ expect(resolved).toHaveLength(2);
459
+ expect(resolved.map((item) => item.uri)).toEqual([
460
+ "mock://item1",
461
+ "file://config.py",
462
+ ]);
463
+ expect(mockGetItems).toHaveBeenCalledTimes(1);
464
+ expect(fileGetItems).toHaveBeenCalledTimes(1);
465
+ });
466
+
467
+ it("should not call unrelated providers", () => {
468
+ registry.resolveItems(["mock://item1"] as ContextLocatorId[]);
469
+
470
+ expect(mockGetItems).toHaveBeenCalledTimes(1);
471
+ expect(fileGetItems).not.toHaveBeenCalled();
472
+ });
473
+
474
+ it("should ignore unknown or invalid URIs", () => {
475
+ const resolved = registry.resolveItems([
476
+ "mock://nonexistent",
477
+ "unknown://item",
478
+ "malformed" as ContextLocatorId,
479
+ ] as ContextLocatorId[]);
480
+
481
+ expect(resolved).toEqual([]);
482
+ expect(mockGetItems).toHaveBeenCalledTimes(1);
483
+ expect(fileGetItems).not.toHaveBeenCalled();
484
+ });
485
+
486
+ it("should return empty array for empty input", () => {
487
+ expect(registry.resolveItems([])).toEqual([]);
488
+ expect(mockGetItems).not.toHaveBeenCalled();
489
+ expect(fileGetItems).not.toHaveBeenCalled();
490
+ });
491
+
492
+ it("should preserve requested order when providers are interleaved", () => {
493
+ const resolved = registry.resolveItems([
494
+ "mock://item1",
495
+ "file://config.py",
496
+ "mock://item2",
497
+ "file://utils/helpers.py",
498
+ ] as ContextLocatorId[]);
499
+
500
+ expect(resolved.map((item) => item.uri)).toEqual([
501
+ "mock://item1",
502
+ "file://config.py",
503
+ "mock://item2",
504
+ "file://utils/helpers.py",
505
+ ]);
506
+ // Each provider is still queried only once, regardless of interleaving.
507
+ expect(mockGetItems).toHaveBeenCalledTimes(1);
508
+ expect(fileGetItems).toHaveBeenCalledTimes(1);
509
+ });
510
+
511
+ it("should resolve items from all providers that share a context type", () => {
512
+ const secondaryProvider = new SecondaryMockProvider();
513
+ const secondaryGetItems = vi.spyOn(secondaryProvider, "getItems");
514
+ registry.register(secondaryProvider);
515
+
516
+ const resolved = registry.resolveItems([
517
+ "mock://item1",
518
+ "mock://secondary-item",
519
+ ] as ContextLocatorId[]);
520
+
521
+ expect(resolved.map((item) => item.uri)).toEqual([
522
+ "mock://item1",
523
+ "mock://secondary-item",
524
+ ]);
525
+ expect(mockGetItems).toHaveBeenCalledTimes(1);
526
+ expect(secondaryGetItems).toHaveBeenCalledTimes(1);
527
+ });
528
+ });
529
+
414
530
  describe("getContextInfo", () => {
415
531
  beforeEach(() => {
416
532
  registry.register(mockProvider);
@@ -531,6 +647,19 @@ describe("AIContextRegistry", () => {
531
647
  expect(formatted).toContain("Mock: Item 1 (value1)");
532
648
  expect(formatted.split("\n\n")).toHaveLength(1);
533
649
  });
650
+
651
+ it("should format items from the provider that owns them", () => {
652
+ registry.register(new SecondaryMockProvider());
653
+
654
+ const formatted = registry.formatContextForAI([
655
+ "mock://item1",
656
+ "mock://secondary-item",
657
+ ] as ContextLocatorId[]);
658
+
659
+ expect(formatted).toBe(
660
+ "Mock: Item 1 (value1)\n\nSecondary: Secondary Item",
661
+ );
662
+ });
534
663
  });
535
664
 
536
665
  describe("edge cases and error handling", () => {
@@ -621,7 +750,7 @@ describe("AIContextRegistry", () => {
621
750
  expect(provider).toBe(errorProvider);
622
751
 
623
752
  const items = registry.getAllItems();
624
- expect(items).toHaveLength(1);
753
+ expect(items).toHaveLength(3);
625
754
  expect(items[0].type).toBe("error");
626
755
  expect(items[0].name).toBe("Errors");
627
756
  });
@@ -654,10 +783,11 @@ describe("AIContextRegistry", () => {
654
783
  expect(provider).toBeDefined();
655
784
 
656
785
  const items = provider!.getItems();
657
- expect(items).toHaveLength(1);
786
+ expect(items).toHaveLength(3);
658
787
 
659
788
  const completion = provider!.formatCompletion(items[0]);
660
789
  expect(completion.label).toBe("@Errors");
790
+ expect(completion.apply).toBe("@error://all");
661
791
  expect(completion.type).toBe("error");
662
792
  });
663
793
 
@@ -690,7 +820,7 @@ describe("AIContextRegistry", () => {
690
820
  const errorItems = items.filter((item) => item.type === "error");
691
821
  const mockItems = items.filter((item) => item.type === "mock");
692
822
 
693
- expect(errorItems).toHaveLength(1);
823
+ expect(errorItems).toHaveLength(3);
694
824
  expect(mockItems).toHaveLength(3);
695
825
  });
696
826
 
@@ -1,6 +1,7 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
  /* oxlint-disable typescript/no-explicit-any */
3
3
 
4
+ import { createStore } from "jotai";
4
5
  import { beforeEach, describe, expect, it } from "vitest";
5
6
  import { cellId, variableName } from "@/__tests__/branded";
6
7
  import type {
@@ -634,7 +635,8 @@ describe("DatasourceContextProvider", () => {
634
635
 
635
636
  describe("getDatasourceContext", () => {
636
637
  it("should return null if no cell ID is found", () => {
637
- const context = getDatasourceContext(cellId("1"));
638
+ const store = createStore();
639
+ const context = getDatasourceContext(cellId("1"), store);
638
640
  expect(context).toBeNull();
639
641
  });
640
642
  });