@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.
@@ -9,11 +9,19 @@ import { MultiMap } from "@/utils/multi-map";
9
9
  import type { TypedString } from "@/utils/typed";
10
10
 
11
11
  /**
12
- * Unique identifier for a context item in the format "type:id"
12
+ * Unique identifier for a context item in the format "type://id"
13
13
  * e.g., "variable://my_var", "data://users", "file://config.py"
14
14
  */
15
15
  export type ContextLocatorId = TypedString<"ContextLocatorId">;
16
16
 
17
+ function parseContextType(uri: ContextLocatorId): string | undefined {
18
+ const separator = uri.indexOf("://");
19
+ if (separator === -1) {
20
+ return undefined;
21
+ }
22
+ return uri.slice(0, separator);
23
+ }
24
+
17
25
  /**
18
26
  * Base interface for context items that can be mentioned in AI prompts
19
27
  */
@@ -142,40 +150,80 @@ export class AIContextRegistry<T extends AIContextItem> {
142
150
  );
143
151
  }
144
152
 
153
+ private findProviderForUri(
154
+ uri: ContextLocatorId,
155
+ ): AIContextProvider | undefined {
156
+ const type = parseContextType(uri);
157
+ if (!type) {
158
+ return undefined;
159
+ }
160
+ return [...this.providers].find(
161
+ (provider) =>
162
+ provider.contextType === type &&
163
+ provider.getItems().some((item) => item.uri === uri),
164
+ );
165
+ }
166
+
145
167
  /**
146
- * Get context information for mentioned items
168
+ * Resolve only the requested context items, querying each matching provider
147
169
  */
148
- getContextInfo(contextIds: ContextLocatorId[]): T[] {
149
- const contextInfo: T[] = [];
150
- const allItems = new Map<ContextLocatorId, T>(
151
- this.getAllItems().map((item) => [item.uri as ContextLocatorId, item]),
152
- );
170
+ resolveItems(contextIds: ContextLocatorId[]): T[] {
171
+ if (contextIds.length === 0) {
172
+ return [];
173
+ }
174
+
175
+ const idsByType = new MultiMap<string, ContextLocatorId>();
176
+ for (const contextId of contextIds) {
177
+ const type = parseContextType(contextId);
178
+ if (type) {
179
+ idsByType.add(type, contextId);
180
+ }
181
+ }
182
+
183
+ const itemsById = new Map<ContextLocatorId, T>();
184
+ for (const [type, ids] of idsByType.entries()) {
185
+ const providers = [...this.providers].filter(
186
+ (provider) => provider.contextType === type,
187
+ );
188
+ for (const provider of providers) {
189
+ const itemsByUri = new Map<ContextLocatorId, T>(
190
+ provider
191
+ .getItems()
192
+ .map((item) => [item.uri as ContextLocatorId, item as T]),
193
+ );
194
+ for (const id of ids) {
195
+ const item = itemsByUri.get(id);
196
+ if (item) {
197
+ itemsById.set(id, item);
198
+ }
199
+ }
200
+ }
201
+ }
153
202
 
203
+ // Preserve the order in which the ids were requested, so formatted context
204
+ // matches the order the user mentioned them in the prompt.
205
+ const results: T[] = [];
154
206
  for (const contextId of contextIds) {
155
- const item = allItems.get(contextId);
207
+ const item = itemsById.get(contextId);
156
208
  if (item) {
157
- contextInfo.push(item);
209
+ results.push(item);
158
210
  }
159
211
  }
212
+ return results;
213
+ }
160
214
 
161
- return contextInfo;
215
+ /**
216
+ * Get context information for mentioned items
217
+ */
218
+ getContextInfo(contextIds: ContextLocatorId[]): T[] {
219
+ return this.resolveItems(contextIds);
162
220
  }
163
221
 
164
222
  /**
165
223
  * Format context for AI prompt inclusion
166
224
  */
167
225
  formatContextForAI(contextIds: ContextLocatorId[]): string {
168
- const allItems = new Map<ContextLocatorId, T>(
169
- this.getAllItems().map((item) => [item.uri as ContextLocatorId, item]),
170
- );
171
-
172
- const contextInfo: T[] = [];
173
- for (const contextId of contextIds) {
174
- const item = allItems.get(contextId);
175
- if (item) {
176
- contextInfo.push(item);
177
- }
178
- }
226
+ const contextInfo = this.resolveItems(contextIds);
179
227
 
180
228
  if (contextInfo.length === 0) {
181
229
  return "";
@@ -183,7 +231,7 @@ export class AIContextRegistry<T extends AIContextItem> {
183
231
 
184
232
  return contextInfo
185
233
  .map((item) => {
186
- const provider = this.getProvider(item.type);
234
+ const provider = this.findProviderForUri(item.uri as ContextLocatorId);
187
235
  return provider?.formatContext(item) || "";
188
236
  })
189
237
  .join("\n\n");
@@ -195,36 +243,22 @@ export class AIContextRegistry<T extends AIContextItem> {
195
243
  async getAttachmentsForContext(
196
244
  contextIds: ContextLocatorId[],
197
245
  ): Promise<FileUIPart[]> {
198
- const allItems = new Map<ContextLocatorId, T>(
199
- this.getAllItems().map((item) => [item.uri as ContextLocatorId, item]),
200
- );
201
-
202
- const contextInfo: T[] = [];
203
- for (const contextId of contextIds) {
204
- const item = allItems.get(contextId);
205
- if (item) {
206
- contextInfo.push(item);
207
- }
208
- }
246
+ const contextInfo = this.resolveItems(contextIds);
209
247
 
210
248
  if (contextInfo.length === 0) {
211
249
  return [];
212
250
  }
213
251
 
214
- // Group items by provider type to batch attachment requests
215
- const itemsByProvider = new MultiMap<string, T>();
252
+ const itemsByProvider = new MultiMap<AIContextProvider, T>();
216
253
  for (const item of contextInfo) {
217
- const providerType = item.type;
218
- itemsByProvider.add(providerType, item);
254
+ const provider = this.findProviderForUri(item.uri as ContextLocatorId);
255
+ if (provider) {
256
+ itemsByProvider.add(provider, item);
257
+ }
219
258
  }
220
259
 
221
- // Collect attachments from all providers
222
260
  const attachmentPromises = [...itemsByProvider.entries()].map(
223
- async ([providerType, items]) => {
224
- const provider = this.getProvider(providerType);
225
- if (!provider) {
226
- return [];
227
- }
261
+ async ([provider, items]) => {
228
262
  try {
229
263
  return await provider.getAttachments(items);
230
264
  } catch (error) {
@@ -11,6 +11,7 @@ import {
11
11
  resourceDecorations,
12
12
  resourceInputFilter,
13
13
  resourcesField,
14
+ resourceSync,
14
15
  resourceTheme,
15
16
  } from "@marimo-team/codemirror-mcp";
16
17
  import {
@@ -38,25 +39,28 @@ const NONE_RESOURCE_FORMAT_COMPLETION = {
38
39
  },
39
40
  };
40
41
 
42
+ function getRegistryResources(store: JotaiStore): Resource[] {
43
+ const registry = getAIContextRegistry(store);
44
+ const resources = registry.getAllItems();
45
+ if (resources.length === 0) {
46
+ return NONE_RESOURCE;
47
+ }
48
+ return resources;
49
+ }
50
+
41
51
  export function resourceExtension(opts: {
42
52
  language: Language;
43
53
  store: JotaiStore;
44
54
  onAddFiles?: (files: File[]) => void;
45
55
  }): Extension[] {
46
56
  const { language, store, onAddFiles } = opts;
57
+ const getResources = () => getRegistryResources(store);
47
58
 
48
59
  return [
49
60
  language.data.of({
50
61
  // Resource completion for static resources (variables, tables, etc.)
51
62
  autocomplete: resourceCompletion(
52
- async (): Promise<Resource[]> => {
53
- const registry = getAIContextRegistry(store);
54
- const resources = registry.getAllItems();
55
- if (resources.length === 0) {
56
- return NONE_RESOURCE;
57
- }
58
- return resources;
59
- },
63
+ async (): Promise<Resource[]> => getResources(),
60
64
  (resource) => {
61
65
  if (resource.type === NONE_RESOURCE_TYPE) {
62
66
  return NONE_RESOURCE_FORMAT_COMPLETION;
@@ -92,9 +96,10 @@ export function resourceExtension(opts: {
92
96
  : []),
93
97
  resourceDecorations,
94
98
  resourceInputFilter,
99
+ // Resolve @-mention chips for programmatic prefills.
100
+ resourceSync(getResources, { logger: Logger }),
95
101
  resourcesField.init(() => {
96
- const registry = getAIContextRegistry(store);
97
- const resources = registry.getAllItems();
102
+ const resources = getRegistryResources(store);
98
103
  return new Map(resources.map((resource) => [resource.uri, resource]));
99
104
  }),
100
105
  resourceTheme,
@@ -6,6 +6,7 @@ import type { AiCompletionCell } from "../ai/state";
6
6
  import type { CellId } from "../cells/ids";
7
7
  import type { MarimoError } from "../kernel/messages";
8
8
  import { wrapInFunction } from "./utils";
9
+ import { store } from "../state/jotai";
9
10
 
10
11
  interface AIFix {
11
12
  setAiCompletionCell: (opts: AiCompletionCell) => void;
@@ -84,7 +85,7 @@ export function getAutoFixes(
84
85
  description: "Fix the SQL statement",
85
86
  fixType: "ai",
86
87
  onFix: async (ctx) => {
87
- const datasourceContext = getDatasourceContext(ctx.cellId);
88
+ const datasourceContext = getDatasourceContext(ctx.cellId, store);
88
89
  let initialPrompt = `Fix the SQL statement: ${error.msg}.`;
89
90
  if (datasourceContext) {
90
91
  initialPrompt += `\nDatabase schema: ${datasourceContext}`;
package/src/core/mime.ts CHANGED
@@ -1,9 +1,16 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
  import type { OutputMessage } from "./kernel/messages";
3
3
 
4
+ export function isMarimoErrorsMime(
5
+ mime: OutputMessage["mimetype"] | undefined,
6
+ ) {
7
+ return mime === "application/vnd.marimo+error";
8
+ }
9
+
10
+ export function isTracebackMime(mime: OutputMessage["mimetype"] | undefined) {
11
+ return mime === "application/vnd.marimo+traceback";
12
+ }
13
+
4
14
  export function isErrorMime(mime: OutputMessage["mimetype"] | undefined) {
5
- return (
6
- mime === "application/vnd.marimo+error" ||
7
- mime === "application/vnd.marimo+traceback"
8
- );
15
+ return isMarimoErrorsMime(mime) || isTracebackMime(mime);
9
16
  }
@@ -1,3 +0,0 @@
1
- // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
-
3
- exports[`ErrorContextProvider > formatContext > should format context for basic errors > basic-error-context 1`] = `"<error name="Cell 1" description="Invalid syntax"></error>"`;