@marimo-team/frontend 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.
- package/dist/assets/{CellStatus-B3d9WY2x.js → CellStatus-CSZHI8sz.js} +1 -1
- package/dist/assets/{JsonOutput-C3c9GGaV.js → JsonOutput-CYIo9GSP.js} +12 -12
- package/dist/assets/MarimoErrorOutput-BokuSnpU.js +11 -0
- package/dist/assets/{RenderHTML-v15QgtRg.js → RenderHTML-B9mw7TD1.js} +1 -1
- package/dist/assets/{RunButton-Bz4eaTJM.js → RunButton-CX_nNY62.js} +1 -1
- package/dist/assets/{add-cell-with-ai-wau9Yzcj.js → add-cell-with-ai-DwzlK27e.js} +47 -34
- package/dist/assets/{add-connection-dialog-_KCPEn7U.js → add-connection-dialog-fmiSisf0.js} +6 -6
- package/dist/assets/{agent-panel-Bn9AgwJH.js → agent-panel-BDO44WWH.js} +5 -5
- package/dist/assets/{ai-model-dropdown-BeZrAec-.js → ai-model-dropdown-BP8gjnOy.js} +3 -3
- package/dist/assets/{app-config-button-BG9ONWMU.js → app-config-button-CVpG64c2.js} +1 -1
- package/dist/assets/{cell-editor-24ykqaHj.js → cell-editor-Bbir67_U.js} +10 -10
- package/dist/assets/cell-link-CBRggkzf.js +1 -0
- package/dist/assets/{cells-DENsmH9t.js → cells-RJTD-euN.js} +44 -44
- package/dist/assets/{chat-display-CcL_dydg.js → chat-display-17k5Uy0b.js} +1 -1
- package/dist/assets/{chat-panel-CA-2-S1C.js → chat-panel-CGwZggen.js} +2 -2
- package/dist/assets/{chat-ui-DQeA0_b5.js → chat-ui-BrSyHh93.js} +1 -1
- package/dist/assets/{column-preview-BPgYXdDd.js → column-preview-dW7xtQ7S.js} +1 -1
- package/dist/assets/{command-palette-WlkKgdrI.js → command-palette-DtKhXIMn.js} +1 -1
- package/dist/assets/{common-DdBQICbD.js → common-DHzo8K8C.js} +1 -1
- package/dist/assets/components-ChBJEsmj.js +1 -0
- package/dist/assets/datasource-RP7m6Xrh.js +3 -0
- package/dist/assets/{dependency-graph-panel-nYHPHFU4.js → dependency-graph-panel-CjdEazj5.js} +4 -4
- package/dist/assets/{documentation-panel-Dw9tYBNf.js → documentation-panel-BcUF41gA.js} +1 -1
- package/dist/assets/{download-4NoBestd.js → download-CJ6tdZ-1.js} +4 -4
- package/dist/assets/{edit-page-CAfBRrHn.js → edit-page-lo6LC4Ie.js} +6 -6
- package/dist/assets/{error-panel-Ddr_mbqR.js → error-panel-D_1OmgVJ.js} +1 -1
- package/dist/assets/{file-explorer-panel-DhpN8_pR.js → file-explorer-panel-CG5na8ox.js} +3 -3
- package/dist/assets/{file-icons-BFLcd4J7.js → file-icons-CGJ6uWXm.js} +1 -1
- package/dist/assets/{file-name-input-x53prQQ6.js → file-name-input-DC8-M5r1.js} +1 -1
- package/dist/assets/{floating-outline--OlHG_yX.js → floating-outline-BOkj3eC_.js} +1 -1
- package/dist/assets/{focus-CZUEQpxX.js → focus-BViZMdHR.js} +1 -1
- package/dist/assets/{form-CJ3pB1ow.js → form-BbqpGicG.js} +2 -2
- package/dist/assets/{home-page-pH8-XovY.js → home-page-CU6li-ts.js} +2 -2
- package/dist/assets/{hooks-BqjZMbeu.js → hooks-CPpze3O4.js} +1 -1
- package/dist/assets/{html-to-image-B6eCuh-2.js → html-to-image-B8ja7wwq.js} +2 -2
- package/dist/assets/{index-CNmQbGXF.js → index-BcxGcwkJ.js} +6 -6
- package/dist/assets/kiosk-mode-BP35ALfP.js +1 -0
- package/dist/assets/{layout-DLJQyOOv.js → layout-BSHt96Sb.js} +5 -5
- package/dist/assets/{logs-panel-BBGNeMzu.js → logs-panel-CbZLi2XS.js} +1 -1
- package/dist/assets/{markdown-renderer-CcLl4-oB.js → markdown-renderer-jghC8PFL.js} +2 -2
- package/dist/assets/{name-cell-input-CNq9BTL7.js → name-cell-input-CN4MRZOm.js} +1 -1
- package/dist/assets/{outline-panel-CnGBTEs0.js → outline-panel-CvB3hUJ2.js} +1 -1
- package/dist/assets/{packages-panel-B17awHIF.js → packages-panel-BGgZLApn.js} +1 -1
- package/dist/assets/{panels-P8wFgP-8.js → panels-DCWntQim.js} +1 -1
- package/dist/assets/process-output-xDK6-i-G.js +1 -0
- package/dist/assets/{radio-group-B8OldelF.js → radio-group-DFkF8CeQ.js} +1 -1
- package/dist/assets/{readonly-code-display-bvftv2Nk.js → readonly-code-display-BHx0vyug.js} +1 -1
- package/dist/assets/{readonly-python-code-CoFR55Yt.js → readonly-python-code-Cla-uJpn.js} +1 -1
- package/dist/assets/{reveal-component-CfBFUsP0.js → reveal-component-Bvxyfpeh.js} +15 -15
- package/dist/assets/{run-page-CFoRuGGY.js → run-page-Cxc4jqCp.js} +1 -1
- package/dist/assets/scratchpad-panel-Bp4sg_00.js +1 -0
- package/dist/assets/{session-panel-D5OQ5JSm.js → session-panel-WkryRIGA.js} +1 -1
- package/dist/assets/{snippets-panel-DvfXacKM.js → snippets-panel-BcDwLUNv.js} +1 -1
- package/dist/assets/{state-CQn0dE8X.js → state-CXzKtmF5.js} +2 -2
- package/dist/assets/{state-LM4pQmYL.js → state-DHqaTLr2.js} +1 -1
- package/dist/assets/{textarea-Tswrj-fB.js → textarea-B5Bp08T3.js} +1 -1
- package/dist/assets/{tracing-BMvzhCBv.js → tracing-clJBeQm6.js} +1 -1
- package/dist/assets/{tracing-panel-DJF6cCQK.js → tracing-panel-7j9NY9lO.js} +2 -2
- package/dist/assets/{tree-actions-Bnmh7YGo.js → tree-actions-DLySKXUq.js} +1 -1
- package/dist/assets/useCellActionButton-CvQRbg1P.js +1 -0
- package/dist/assets/{useDeleteCell-DwNTIWuB.js → useDeleteCell-Sj06CzJn.js} +1 -1
- package/dist/assets/{useDependencyPanelTab-BH_Eu2Kq.js → useDependencyPanelTab-BTgQmu1R.js} +1 -1
- package/dist/assets/{useNotebookActions-BmO8IOgN.js → useNotebookActions-9PPlpXk8.js} +1 -1
- package/dist/assets/useRunCells-D5aAUcAa.js +1 -0
- package/dist/assets/useSplitCell-CGE6jkuF.js +1 -0
- package/dist/index.html +22 -22
- package/package.json +2 -2
- package/src/components/editor/errors/__tests__/auto-fix.test.ts +11 -107
- package/src/components/editor/errors/auto-fix.tsx +11 -31
- package/src/components/editor/output/MarimoTracebackOutput.tsx +2 -2
- package/src/core/ai/context/__tests__/registry.test.ts +133 -3
- package/src/core/ai/context/providers/__tests__/datasource.test.ts +3 -1
- package/src/core/ai/context/providers/__tests__/error.test.ts +200 -15
- package/src/core/ai/context/providers/datasource.ts +27 -2
- package/src/core/ai/context/providers/error.ts +226 -36
- package/src/core/ai/context/registry.ts +77 -43
- package/src/core/codemirror/ai/resources.ts +15 -10
- package/src/core/errors/errors.ts +2 -1
- package/src/core/mime.ts +11 -4
- package/dist/assets/MarimoErrorOutput-D07aAGkQ.js +0 -12
- package/dist/assets/cell-link-exzcyUhT.js +0 -1
- package/dist/assets/components-DinLkTiR.js +0 -1
- package/dist/assets/datasource-BCOYpQ69.js +0 -3
- package/dist/assets/kiosk-mode-BBfHxqEx.js +0 -1
- package/dist/assets/process-output-BGcDSQBk.js +0 -1
- package/dist/assets/scratchpad-panel-DdKvJKy4.js +0 -1
- package/dist/assets/useCellActionButton-ibDHNca0.js +0 -1
- package/dist/assets/useRunCells-CTpvxsqp.js +0 -1
- package/dist/assets/useSplitCell-CFO4Cg5B.js +0 -1
- package/src/core/ai/context/providers/__tests__/__snapshots__/error.test.ts.snap +0 -3
|
@@ -57,7 +57,7 @@ describe("ErrorContextProvider", () => {
|
|
|
57
57
|
|
|
58
58
|
const items = provider.getItems();
|
|
59
59
|
|
|
60
|
-
expect(items).toHaveLength(
|
|
60
|
+
expect(items).toHaveLength(3);
|
|
61
61
|
expect(items[0]).toMatchObject({
|
|
62
62
|
name: "Errors",
|
|
63
63
|
type: "error",
|
|
@@ -78,6 +78,18 @@ describe("ErrorContextProvider", () => {
|
|
|
78
78
|
],
|
|
79
79
|
},
|
|
80
80
|
});
|
|
81
|
+
expect(items[1]).toMatchObject({
|
|
82
|
+
uri: `error://${cellId1}`,
|
|
83
|
+
name: "Error: Cell 1",
|
|
84
|
+
description: "Invalid syntax",
|
|
85
|
+
data: { type: "cell-error", error: { cellId: cellId1 } },
|
|
86
|
+
});
|
|
87
|
+
expect(items[2]).toMatchObject({
|
|
88
|
+
uri: `error://${cellId2}`,
|
|
89
|
+
name: "Error: Cell 2",
|
|
90
|
+
description: "Runtime error",
|
|
91
|
+
data: { type: "cell-error", error: { cellId: cellId2 } },
|
|
92
|
+
});
|
|
81
93
|
});
|
|
82
94
|
|
|
83
95
|
it("should handle cells without names", () => {
|
|
@@ -92,7 +104,12 @@ describe("ErrorContextProvider", () => {
|
|
|
92
104
|
]);
|
|
93
105
|
|
|
94
106
|
const items = provider.getItems();
|
|
95
|
-
expect(items[
|
|
107
|
+
expect(items[1].data.type).toBe("cell-error");
|
|
108
|
+
if (items[1].data.type === "cell-error") {
|
|
109
|
+
expect(items[1].data.error.cellName).toBe("cell-0");
|
|
110
|
+
}
|
|
111
|
+
expect(items[1].name).toBe("Error: cell-0");
|
|
112
|
+
expect(items[1].description).toBe("Invalid syntax");
|
|
96
113
|
});
|
|
97
114
|
});
|
|
98
115
|
|
|
@@ -119,7 +136,7 @@ describe("ErrorContextProvider", () => {
|
|
|
119
136
|
|
|
120
137
|
expect(completion).toMatchInlineSnapshot(`
|
|
121
138
|
{
|
|
122
|
-
"apply": "@
|
|
139
|
+
"apply": "@error://all",
|
|
123
140
|
"detail": "2 errors",
|
|
124
141
|
"displayLabel": "Errors",
|
|
125
142
|
"info": [Function],
|
|
@@ -170,6 +187,24 @@ describe("ErrorContextProvider", () => {
|
|
|
170
187
|
expect(completion.detail).toBe("1 error");
|
|
171
188
|
});
|
|
172
189
|
|
|
190
|
+
it("formats completion for a single cell error", () => {
|
|
191
|
+
const cellId = CellIdClass.create();
|
|
192
|
+
|
|
193
|
+
createMockNotebookWithErrors([
|
|
194
|
+
{
|
|
195
|
+
cellId,
|
|
196
|
+
cellName: "Cell 1",
|
|
197
|
+
errorData: [MockNotebook.errors.syntax("Invalid syntax")],
|
|
198
|
+
},
|
|
199
|
+
]);
|
|
200
|
+
|
|
201
|
+
const items = provider.getItems();
|
|
202
|
+
const completion = provider.formatCompletion(items[1]);
|
|
203
|
+
expect(completion.apply).toBe(`@error://${cellId}`);
|
|
204
|
+
expect(completion.displayLabel).toBe("Error: Cell 1");
|
|
205
|
+
expect(items[1].name).toBe("Error: Cell 1");
|
|
206
|
+
});
|
|
207
|
+
|
|
173
208
|
it("should handle fallback for unknown error types", () => {
|
|
174
209
|
const item = {
|
|
175
210
|
uri: "error://unknown",
|
|
@@ -210,7 +245,8 @@ describe("ErrorContextProvider", () => {
|
|
|
210
245
|
|
|
211
246
|
const items = provider.getItems();
|
|
212
247
|
const context = provider.formatContext(items[0]);
|
|
213
|
-
expect(context).
|
|
248
|
+
expect(context).toContain("Invalid syntax");
|
|
249
|
+
expect(context).toContain("Code:");
|
|
214
250
|
});
|
|
215
251
|
|
|
216
252
|
it("should format context for multiple error types", () => {
|
|
@@ -239,14 +275,12 @@ describe("ErrorContextProvider", () => {
|
|
|
239
275
|
const items = provider.getItems();
|
|
240
276
|
const context = provider.formatContext(items[0]);
|
|
241
277
|
|
|
242
|
-
|
|
243
|
-
expect(context).
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
The variable 'variable_x' was defined by another cell"></error>"
|
|
249
|
-
`);
|
|
278
|
+
expect(context).toContain("Invalid syntax");
|
|
279
|
+
expect(context).toContain("Runtime error");
|
|
280
|
+
expect(context).toContain("This cell is in a cycle");
|
|
281
|
+
expect(context).toContain(
|
|
282
|
+
"The variable 'variable_x' was defined by another cell",
|
|
283
|
+
);
|
|
250
284
|
});
|
|
251
285
|
|
|
252
286
|
it("should handle cells without names", () => {
|
|
@@ -261,8 +295,158 @@ describe("ErrorContextProvider", () => {
|
|
|
261
295
|
]);
|
|
262
296
|
|
|
263
297
|
const items = provider.getItems();
|
|
264
|
-
const context = provider.formatContext(items[
|
|
265
|
-
expect(context).toContain(`
|
|
298
|
+
const context = provider.formatContext(items[1]);
|
|
299
|
+
expect(context).toContain(`cellId="${cellId}"`);
|
|
300
|
+
expect(context).toContain("Invalid syntax");
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it("includes exception tracebacks in formatted context", () => {
|
|
304
|
+
const cellId = CellIdClass.create();
|
|
305
|
+
|
|
306
|
+
createMockNotebookWithErrors([
|
|
307
|
+
{
|
|
308
|
+
cellId,
|
|
309
|
+
cellName: "Cell 1",
|
|
310
|
+
errorData: [
|
|
311
|
+
{
|
|
312
|
+
type: "exception",
|
|
313
|
+
msg: "boom",
|
|
314
|
+
exception_type: "ValueError",
|
|
315
|
+
raising_cell: null,
|
|
316
|
+
traceback: "<pre>line 1\nline 2</pre>",
|
|
317
|
+
},
|
|
318
|
+
],
|
|
319
|
+
},
|
|
320
|
+
]);
|
|
321
|
+
|
|
322
|
+
const items = provider.getItems();
|
|
323
|
+
const context = provider.formatContext(items[1]);
|
|
324
|
+
expect(context).toContain("boom");
|
|
325
|
+
expect(context).toContain("line 1");
|
|
326
|
+
expect(context).toContain("line 2");
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it("includes traceback-only cell outputs", () => {
|
|
330
|
+
const cellId = CellIdClass.create();
|
|
331
|
+
const notebookState = MockNotebook.notebookState({
|
|
332
|
+
cellData: {
|
|
333
|
+
[cellId]: {
|
|
334
|
+
name: "Cell 1",
|
|
335
|
+
code: "raise ValueError('boom')",
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
});
|
|
339
|
+
notebookState.cellRuntime[cellId] = {
|
|
340
|
+
...notebookState.cellRuntime[cellId],
|
|
341
|
+
output: {
|
|
342
|
+
channel: "marimo-error",
|
|
343
|
+
data: "<pre>ValueError: boom\n line 1</pre>",
|
|
344
|
+
mimetype: "application/vnd.marimo+traceback",
|
|
345
|
+
timestamp: Date.now(),
|
|
346
|
+
},
|
|
347
|
+
};
|
|
348
|
+
store.set(notebookAtom, notebookState);
|
|
349
|
+
|
|
350
|
+
const items = provider.getItems();
|
|
351
|
+
expect(items).toHaveLength(2);
|
|
352
|
+
expect(items[1].description).toBe("ValueError: boom");
|
|
353
|
+
const context = provider.formatContext(items[1]);
|
|
354
|
+
expect(context).toContain("raise ValueError('boom')");
|
|
355
|
+
expect(context).toContain("ValueError: boom");
|
|
356
|
+
expect(context).toContain("line 1");
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it("includes console tracebacks for marimo error outputs", () => {
|
|
360
|
+
const cellId = CellIdClass.create();
|
|
361
|
+
const notebookState = MockNotebook.notebookState({
|
|
362
|
+
cellData: {
|
|
363
|
+
[cellId]: {
|
|
364
|
+
name: "Cell 1",
|
|
365
|
+
code: "raise ValueError('boom')",
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
});
|
|
369
|
+
notebookState.cellRuntime[cellId] = {
|
|
370
|
+
...notebookState.cellRuntime[cellId],
|
|
371
|
+
output: {
|
|
372
|
+
channel: "marimo-error",
|
|
373
|
+
data: [
|
|
374
|
+
{
|
|
375
|
+
type: "exception",
|
|
376
|
+
msg: "boom",
|
|
377
|
+
exception_type: "ValueError",
|
|
378
|
+
raising_cell: null,
|
|
379
|
+
},
|
|
380
|
+
],
|
|
381
|
+
mimetype: "application/vnd.marimo+error",
|
|
382
|
+
timestamp: Date.now(),
|
|
383
|
+
},
|
|
384
|
+
consoleOutputs: [
|
|
385
|
+
{
|
|
386
|
+
channel: "stderr",
|
|
387
|
+
data: '<pre>ValueError: boom\n File "notebook.py", line 1</pre>',
|
|
388
|
+
mimetype: "application/vnd.marimo+traceback",
|
|
389
|
+
timestamp: Date.now(),
|
|
390
|
+
},
|
|
391
|
+
],
|
|
392
|
+
};
|
|
393
|
+
store.set(notebookAtom, notebookState);
|
|
394
|
+
|
|
395
|
+
const items = provider.getItems();
|
|
396
|
+
const context = provider.formatContext(items[1]);
|
|
397
|
+
expect(context).toContain("boom");
|
|
398
|
+
expect(context).toContain("ValueError: boom");
|
|
399
|
+
expect(context).toContain('File "notebook.py", line 1');
|
|
400
|
+
expect(items[1].description).toBe("boom");
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it("does not duplicate console traceback when error already has one", () => {
|
|
404
|
+
const cellId = CellIdClass.create();
|
|
405
|
+
const notebookState = MockNotebook.notebookState({
|
|
406
|
+
cellData: {
|
|
407
|
+
[cellId]: {
|
|
408
|
+
name: "Cell 1",
|
|
409
|
+
code: "raise ValueError('boom')",
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
});
|
|
413
|
+
notebookState.cellRuntime[cellId] = {
|
|
414
|
+
...notebookState.cellRuntime[cellId],
|
|
415
|
+
output: {
|
|
416
|
+
channel: "marimo-error",
|
|
417
|
+
data: [
|
|
418
|
+
{
|
|
419
|
+
type: "exception",
|
|
420
|
+
msg: "boom",
|
|
421
|
+
exception_type: "ValueError",
|
|
422
|
+
raising_cell: null,
|
|
423
|
+
traceback: "<pre>embedded line 1</pre>",
|
|
424
|
+
},
|
|
425
|
+
],
|
|
426
|
+
mimetype: "application/vnd.marimo+error",
|
|
427
|
+
timestamp: Date.now(),
|
|
428
|
+
},
|
|
429
|
+
consoleOutputs: [
|
|
430
|
+
{
|
|
431
|
+
channel: "stderr",
|
|
432
|
+
data: "<pre>console line 1</pre>",
|
|
433
|
+
mimetype: "application/vnd.marimo+traceback",
|
|
434
|
+
timestamp: Date.now(),
|
|
435
|
+
},
|
|
436
|
+
],
|
|
437
|
+
};
|
|
438
|
+
store.set(notebookAtom, notebookState);
|
|
439
|
+
|
|
440
|
+
const items = provider.getItems();
|
|
441
|
+
if (items[1].data.type !== "cell-error") {
|
|
442
|
+
throw new Error("Expected cell-error item");
|
|
443
|
+
}
|
|
444
|
+
expect(items[1].data.error.tracebackHtml).toBeUndefined();
|
|
445
|
+
|
|
446
|
+
const context = provider.formatContext(items[1]);
|
|
447
|
+
expect(context).toContain("embedded line 1");
|
|
448
|
+
expect(context).not.toContain("console line 1");
|
|
449
|
+
expect(items[1].description).toBe("boom");
|
|
266
450
|
});
|
|
267
451
|
});
|
|
268
452
|
|
|
@@ -319,7 +503,8 @@ describe("ErrorContextProvider", () => {
|
|
|
319
503
|
testStore.set(notebookAtom, notebookState);
|
|
320
504
|
|
|
321
505
|
const items = testProvider.getItems();
|
|
322
|
-
|
|
506
|
+
// One "all-errors" item plus one per-cell error item.
|
|
507
|
+
expect(items).toHaveLength(2);
|
|
323
508
|
const context = testProvider.formatContext(items[0]);
|
|
324
509
|
expect(context).toContain(expected);
|
|
325
510
|
}
|
|
@@ -8,6 +8,7 @@ import type { CellId } from "@/core/cells/ids";
|
|
|
8
8
|
import { LanguageAdapters } from "@/core/codemirror/language/LanguageAdapters";
|
|
9
9
|
import { renderDatasourceInfo } from "@/core/codemirror/language/languages/sql/renderers";
|
|
10
10
|
import {
|
|
11
|
+
allTablesAtom,
|
|
11
12
|
type ConnectionsMap,
|
|
12
13
|
type DatasetTablesMap,
|
|
13
14
|
dataSourceConnectionsAtom,
|
|
@@ -18,7 +19,7 @@ import {
|
|
|
18
19
|
INTERNAL_SQL_ENGINES,
|
|
19
20
|
} from "@/core/datasets/engines";
|
|
20
21
|
import type { DataSourceConnection, DataTable } from "@/core/kernel/messages";
|
|
21
|
-
import {
|
|
22
|
+
import type { JotaiStore } from "@/core/state/jotai";
|
|
22
23
|
import type { AIContextItem } from "../registry";
|
|
23
24
|
import { AIContextProvider } from "../registry";
|
|
24
25
|
import { contextToXml } from "../utils";
|
|
@@ -148,7 +149,10 @@ export class DatasourceContextProvider extends AIContextProvider<DatasourceConte
|
|
|
148
149
|
}
|
|
149
150
|
}
|
|
150
151
|
|
|
151
|
-
export function getDatasourceContext(
|
|
152
|
+
export function getDatasourceContext(
|
|
153
|
+
cellId: CellId,
|
|
154
|
+
store: JotaiStore,
|
|
155
|
+
): string | null {
|
|
152
156
|
const cellData = store.get(cellDataAtom(cellId));
|
|
153
157
|
const code = cellData?.code;
|
|
154
158
|
if (!code || code.trim() === "") {
|
|
@@ -164,3 +168,24 @@ export function getDatasourceContext(cellId: CellId): string | null {
|
|
|
164
168
|
}
|
|
165
169
|
return null;
|
|
166
170
|
}
|
|
171
|
+
|
|
172
|
+
export function formatDatasourceContextForCell(
|
|
173
|
+
cellId: CellId,
|
|
174
|
+
store: JotaiStore,
|
|
175
|
+
): string | null {
|
|
176
|
+
const mention = getDatasourceContext(cellId, store);
|
|
177
|
+
if (!mention) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const connectionsMap = store.get(dataSourceConnectionsAtom).connectionsMap;
|
|
182
|
+
const tablesMap = store.get(allTablesAtom);
|
|
183
|
+
const provider = new DatasourceContextProvider(connectionsMap, tablesMap);
|
|
184
|
+
const uri = mention.slice(1);
|
|
185
|
+
const item = provider.getItems().find((candidate) => candidate.uri === uri);
|
|
186
|
+
if (!item) {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return provider.formatContext(item);
|
|
191
|
+
}
|
|
@@ -1,26 +1,133 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import type { Completion } from "@codemirror/autocomplete";
|
|
4
|
-
import {
|
|
4
|
+
import { notebookAtom } from "@/core/cells/cells";
|
|
5
5
|
import type { CellId } from "@/core/cells/ids";
|
|
6
|
-
import
|
|
6
|
+
import { displayCellName } from "@/core/cells/names";
|
|
7
|
+
import type { MarimoError, OutputMessage } from "@/core/kernel/messages";
|
|
8
|
+
import { isErrorMime, isMarimoErrorsMime, isTracebackMime } from "@/core/mime";
|
|
7
9
|
import type { JotaiStore } from "@/core/state/jotai";
|
|
8
10
|
import { logNever } from "@/utils/assertNever";
|
|
11
|
+
import { parseHtmlContent } from "@/utils/dom";
|
|
9
12
|
import { PluralWord } from "@/utils/pluralize";
|
|
10
13
|
import { type AIContextItem, AIContextProvider } from "../registry";
|
|
11
14
|
import { contextToXml } from "../utils";
|
|
12
15
|
import { Sections } from "./common";
|
|
16
|
+
import { formatDatasourceContextForCell } from "./datasource";
|
|
17
|
+
|
|
18
|
+
export interface CellErrorEntry {
|
|
19
|
+
cellId: CellId;
|
|
20
|
+
cellName: string;
|
|
21
|
+
cellCode: string;
|
|
22
|
+
errorData: MarimoError[];
|
|
23
|
+
tracebackHtml?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type ErrorContextItemData =
|
|
27
|
+
| {
|
|
28
|
+
type: "all-errors";
|
|
29
|
+
errors: CellErrorEntry[];
|
|
30
|
+
}
|
|
31
|
+
| {
|
|
32
|
+
type: "cell-error";
|
|
33
|
+
error: CellErrorEntry;
|
|
34
|
+
};
|
|
13
35
|
|
|
14
36
|
export interface ErrorContextItem extends AIContextItem {
|
|
15
37
|
type: "error";
|
|
16
|
-
data:
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
38
|
+
data: ErrorContextItemData;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function parseCellErrorOutput(
|
|
42
|
+
output: OutputMessage,
|
|
43
|
+
): Pick<CellErrorEntry, "errorData" | "tracebackHtml"> | null {
|
|
44
|
+
if (isMarimoErrorsMime(output.mimetype)) {
|
|
45
|
+
if (!Array.isArray(output.data)) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const errorData = output.data.filter(
|
|
49
|
+
(error) => !error.type.includes("ancestor"),
|
|
50
|
+
);
|
|
51
|
+
if (errorData.length === 0) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return { errorData };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (isTracebackMime(output.mimetype)) {
|
|
58
|
+
if (typeof output.data !== "string" || output.data.length === 0) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return { errorData: [], tracebackHtml: output.data };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function errorDataHasTraceback(errorData: MarimoError[]): boolean {
|
|
68
|
+
return errorData.some((error) => "traceback" in error && error.traceback);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getTracebackFromConsole(
|
|
72
|
+
consoleOutputs: OutputMessage[] | undefined,
|
|
73
|
+
): string | undefined {
|
|
74
|
+
const tracebackOutput = consoleOutputs?.find((output) =>
|
|
75
|
+
isTracebackMime(output.mimetype),
|
|
76
|
+
);
|
|
77
|
+
if (
|
|
78
|
+
!tracebackOutput ||
|
|
79
|
+
typeof tracebackOutput.data !== "string" ||
|
|
80
|
+
tracebackOutput.data.length === 0
|
|
81
|
+
) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
return tracebackOutput.data;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function resolveTracebackHtml(
|
|
88
|
+
parsed: Pick<CellErrorEntry, "errorData" | "tracebackHtml">,
|
|
89
|
+
consoleOutputs: OutputMessage[] | undefined,
|
|
90
|
+
): string | undefined {
|
|
91
|
+
if (parsed.tracebackHtml) {
|
|
92
|
+
return parsed.tracebackHtml;
|
|
93
|
+
}
|
|
94
|
+
if (errorDataHasTraceback(parsed.errorData)) {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
return getTracebackFromConsole(consoleOutputs);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function getCellErrorEntries(store: JotaiStore): CellErrorEntry[] {
|
|
101
|
+
const { cellIds, cellRuntime, cellData } = store.get(notebookAtom);
|
|
102
|
+
const entries: CellErrorEntry[] = [];
|
|
103
|
+
|
|
104
|
+
for (const [cellIndex, cellId] of cellIds.inOrderIds.entries()) {
|
|
105
|
+
const cell = cellRuntime[cellId];
|
|
106
|
+
const output = cell.output;
|
|
107
|
+
if (!output || !isErrorMime(output.mimetype)) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const parsed = parseCellErrorOutput(output);
|
|
112
|
+
if (!parsed) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const cellName = displayCellName(cellData[cellId].name, cellIndex);
|
|
117
|
+
// Prefer the code from the last execution: runtime errors correspond to
|
|
118
|
+
// the previous run, while `code` may already contain unsaved edits.
|
|
119
|
+
const cellCode = cellData[cellId].lastCodeRun ?? cellData[cellId].code;
|
|
120
|
+
|
|
121
|
+
entries.push({
|
|
122
|
+
cellId,
|
|
123
|
+
cellName,
|
|
124
|
+
cellCode,
|
|
125
|
+
errorData: parsed.errorData,
|
|
126
|
+
tracebackHtml: resolveTracebackHtml(parsed, cell.consoleOutputs),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return entries;
|
|
24
131
|
}
|
|
25
132
|
|
|
26
133
|
function describeError(error: MarimoError): string {
|
|
@@ -67,6 +174,77 @@ function describeError(error: MarimoError): string {
|
|
|
67
174
|
return "Unknown error";
|
|
68
175
|
}
|
|
69
176
|
|
|
177
|
+
function formatSingleError(error: MarimoError): string {
|
|
178
|
+
let text = describeError(error);
|
|
179
|
+
if ("traceback" in error && error.traceback) {
|
|
180
|
+
text += `\n\nTraceback:\n${parseHtmlContent(error.traceback)}`;
|
|
181
|
+
}
|
|
182
|
+
return text;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function formatCellErrorDetails(
|
|
186
|
+
entry: CellErrorEntry,
|
|
187
|
+
store: JotaiStore,
|
|
188
|
+
): string {
|
|
189
|
+
const parts: string[] = [`Code:\n${entry.cellCode}`];
|
|
190
|
+
|
|
191
|
+
if (entry.tracebackHtml) {
|
|
192
|
+
parts.push(`Traceback:\n${parseHtmlContent(entry.tracebackHtml)}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (entry.errorData.length > 0) {
|
|
196
|
+
parts.push(
|
|
197
|
+
entry.errorData.map((error) => formatSingleError(error)).join("\n\n"),
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (entry.errorData.some((error) => error.type === "sql-error")) {
|
|
202
|
+
const datasourceContext = formatDatasourceContextForCell(
|
|
203
|
+
entry.cellId,
|
|
204
|
+
store,
|
|
205
|
+
);
|
|
206
|
+
if (datasourceContext) {
|
|
207
|
+
parts.push(`Database schema:\n${datasourceContext}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return parts.join("\n\n");
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function formatCellErrorXml(entry: CellErrorEntry, store: JotaiStore): string {
|
|
215
|
+
return contextToXml({
|
|
216
|
+
type: "error",
|
|
217
|
+
data: {
|
|
218
|
+
name: entry.cellName || `Cell ${entry.cellId}`,
|
|
219
|
+
cellId: entry.cellId,
|
|
220
|
+
},
|
|
221
|
+
details: formatCellErrorDetails(entry, store),
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function summarizeCellError(entry: CellErrorEntry): string {
|
|
226
|
+
if (entry.errorData.length === 1) {
|
|
227
|
+
return describeError(entry.errorData[0]);
|
|
228
|
+
}
|
|
229
|
+
if (entry.errorData.length > 1) {
|
|
230
|
+
return `${entry.errorData.length} errors`;
|
|
231
|
+
}
|
|
232
|
+
if (entry.tracebackHtml) {
|
|
233
|
+
const text = parseHtmlContent(entry.tracebackHtml).trim();
|
|
234
|
+
const firstLine = text
|
|
235
|
+
.split("\n")
|
|
236
|
+
.find((line) => line.trim())
|
|
237
|
+
?.trim();
|
|
238
|
+
return firstLine || "traceback";
|
|
239
|
+
}
|
|
240
|
+
return "error";
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function errorContextName(entry: CellErrorEntry): string {
|
|
244
|
+
const cellName = entry.cellName || `Cell ${entry.cellId}`;
|
|
245
|
+
return `Error: ${cellName}`;
|
|
246
|
+
}
|
|
247
|
+
|
|
70
248
|
const errorsTxt = new PluralWord("error", "errors");
|
|
71
249
|
export class ErrorContextProvider extends AIContextProvider<ErrorContextItem> {
|
|
72
250
|
readonly title = "Errors";
|
|
@@ -80,39 +258,50 @@ export class ErrorContextProvider extends AIContextProvider<ErrorContextItem> {
|
|
|
80
258
|
}
|
|
81
259
|
|
|
82
260
|
getItems(): ErrorContextItem[] {
|
|
83
|
-
const errors = this.store
|
|
261
|
+
const errors = getCellErrorEntries(this.store);
|
|
262
|
+
|
|
84
263
|
if (errors.length === 0) {
|
|
85
264
|
return [];
|
|
86
265
|
}
|
|
87
266
|
|
|
88
|
-
|
|
267
|
+
const items: ErrorContextItem[] = [
|
|
89
268
|
{
|
|
90
269
|
uri: this.asURI("all"),
|
|
91
270
|
name: "Errors",
|
|
92
271
|
type: this.contextType,
|
|
93
272
|
data: {
|
|
94
273
|
type: "all-errors",
|
|
95
|
-
errors
|
|
96
|
-
cellId: error.cellId,
|
|
97
|
-
cellName: error.cellName,
|
|
98
|
-
errorData: error.output.data,
|
|
99
|
-
})),
|
|
274
|
+
errors,
|
|
100
275
|
},
|
|
101
276
|
description: "All errors in the notebook",
|
|
102
277
|
},
|
|
103
278
|
];
|
|
104
279
|
|
|
105
|
-
|
|
280
|
+
for (const error of errors) {
|
|
281
|
+
items.push({
|
|
282
|
+
uri: this.asURI(error.cellId),
|
|
283
|
+
name: errorContextName(error),
|
|
284
|
+
type: this.contextType,
|
|
285
|
+
data: {
|
|
286
|
+
type: "cell-error",
|
|
287
|
+
error,
|
|
288
|
+
},
|
|
289
|
+
description: summarizeCellError(error),
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return items;
|
|
106
294
|
}
|
|
107
295
|
|
|
108
296
|
formatCompletion(item: ErrorContextItem): Completion {
|
|
109
297
|
if (item.data.type === "all-errors") {
|
|
298
|
+
const errorCount = item.data.errors.length;
|
|
110
299
|
return {
|
|
111
300
|
label: "@Errors",
|
|
112
301
|
displayLabel: "Errors",
|
|
113
|
-
detail: `${
|
|
302
|
+
detail: `${errorCount} ${errorsTxt.pluralize(errorCount)}`,
|
|
114
303
|
type: "error",
|
|
115
|
-
apply: "@
|
|
304
|
+
apply: "@error://all",
|
|
116
305
|
section: Sections.ERROR,
|
|
117
306
|
info: () => {
|
|
118
307
|
const infoContainer = document.createElement("div");
|
|
@@ -136,7 +325,7 @@ export class ErrorContextProvider extends AIContextProvider<ErrorContextItem> {
|
|
|
136
325
|
|
|
137
326
|
const descriptionDiv = document.createElement("div");
|
|
138
327
|
descriptionDiv.classList.add("text-sm", "text-muted-foreground");
|
|
139
|
-
descriptionDiv.textContent = `${
|
|
328
|
+
descriptionDiv.textContent = `${errorCount} ${errorsTxt.pluralize(errorCount)}`;
|
|
140
329
|
headerDiv.append(descriptionDiv);
|
|
141
330
|
|
|
142
331
|
infoContainer.append(headerDiv);
|
|
@@ -146,6 +335,17 @@ export class ErrorContextProvider extends AIContextProvider<ErrorContextItem> {
|
|
|
146
335
|
};
|
|
147
336
|
}
|
|
148
337
|
|
|
338
|
+
if (item.data.type === "cell-error") {
|
|
339
|
+
const { error } = item.data;
|
|
340
|
+
return {
|
|
341
|
+
...this.createBasicCompletion(item, {
|
|
342
|
+
detail: summarizeCellError(error),
|
|
343
|
+
type: "error",
|
|
344
|
+
}),
|
|
345
|
+
section: Sections.ERROR,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
149
349
|
return {
|
|
150
350
|
label: "Error",
|
|
151
351
|
displayLabel: "Error",
|
|
@@ -154,21 +354,11 @@ export class ErrorContextProvider extends AIContextProvider<ErrorContextItem> {
|
|
|
154
354
|
}
|
|
155
355
|
|
|
156
356
|
formatContext(item: ErrorContextItem): string {
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
const xmls = data.errors.map((err) => {
|
|
160
|
-
return contextToXml({
|
|
161
|
-
type: this.contextType,
|
|
162
|
-
data: {
|
|
163
|
-
name: err.cellName || `Cell ${err.cellId}`,
|
|
164
|
-
description: err.errorData
|
|
165
|
-
.map((err) => describeError(err))
|
|
166
|
-
.join("\n"),
|
|
167
|
-
},
|
|
168
|
-
});
|
|
169
|
-
});
|
|
357
|
+
const entries =
|
|
358
|
+
item.data.type === "all-errors" ? item.data.errors : [item.data.error];
|
|
170
359
|
|
|
171
|
-
|
|
172
|
-
|
|
360
|
+
return entries
|
|
361
|
+
.map((entry) => formatCellErrorXml(entry, this.store))
|
|
362
|
+
.join("\n\n");
|
|
173
363
|
}
|
|
174
364
|
}
|