@marimo-team/islands 0.20.5-dev33 → 0.20.5-dev35

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/main.js CHANGED
@@ -70710,7 +70710,7 @@ Image URL: ${r.imageUrl}`)), contextToXml({
70710
70710
  return Logger.warn("Failed to get version from mount config"), null;
70711
70711
  }
70712
70712
  }
70713
- const marimoVersionAtom = atom(getVersionFromMountConfig() || "0.20.5-dev33"), showCodeInRunModeAtom = atom(true);
70713
+ const marimoVersionAtom = atom(getVersionFromMountConfig() || "0.20.5-dev35"), showCodeInRunModeAtom = atom(true);
70714
70714
  atom(null);
70715
70715
  var import_compiler_runtime$89 = require_compiler_runtime();
70716
70716
  function useKeydownOnElement(e, r) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.20.5-dev33",
3
+ "version": "0.20.5-dev35",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -488,6 +488,133 @@ describe("RunStaleCellsTool", () => {
488
488
  // Output should be capped (2000 chars content + "Output:\n" prefix + truncation message)
489
489
  expect(output.length).toBeLessThan(2200);
490
490
  });
491
+
492
+ it("should omit output for cells that exceed total output budget", async () => {
493
+ const cellIds = Array.from(
494
+ { length: 25 },
495
+ (_, i) => `budget-cell-${i}` as CellId,
496
+ );
497
+ const cellData: Record<string, { code: string; edited: boolean }> = {};
498
+ for (const id of cellIds) {
499
+ cellData[id] = { code: "x = 1", edited: true };
500
+ }
501
+
502
+ const notebook = MockNotebook.notebookState({ cellData });
503
+ store.set(notebookAtom, notebook);
504
+
505
+ vi.mocked(runCells).mockImplementation(async () => {
506
+ const updatedNotebook = store.get(notebookAtom);
507
+ for (const id of cellIds) {
508
+ updatedNotebook.cellRuntime[id] = {
509
+ ...updatedNotebook.cellRuntime[id],
510
+ status: "idle",
511
+ };
512
+ }
513
+ store.set(notebookAtom, updatedNotebook);
514
+ });
515
+
516
+ // Each cell produces ~2008 chars of formatted output ("Output:\n" + 2000 chars).
517
+ // After 20 cells the running total exceeds MAX_TOOL_OUTPUT_CHARS (40,000).
518
+ const content = "a".repeat(2000);
519
+ vi.mocked(getCellContextData).mockReturnValue({
520
+ cellOutput: {
521
+ outputType: "text",
522
+ processedContent: content,
523
+ imageUrl: null,
524
+ output: { mimetype: "text/plain", data: content },
525
+ },
526
+ consoleOutputs: null,
527
+ cellName: "cell",
528
+ } as never);
529
+
530
+ const result = await tool.handler({}, toolContext as never);
531
+
532
+ expect(result.cellsToOutput?.[cellIds[0]]?.cellOutput).toContain(
533
+ "Output:",
534
+ );
535
+ expect(result.cellsToOutput?.[cellIds[24]]?.cellOutput).toBe(
536
+ "Cell executed (output omitted due to context limits).",
537
+ );
538
+ });
539
+
540
+ it("should use higher truncation limit for error outputs", async () => {
541
+ const notebook = MockNotebook.notebookState({
542
+ cellData: {
543
+ [cellId1]: { code: "raise Exception()", edited: true },
544
+ },
545
+ });
546
+ store.set(notebookAtom, notebook);
547
+
548
+ vi.mocked(runCells).mockImplementation(async () => {
549
+ const updatedNotebook = store.get(notebookAtom);
550
+ updatedNotebook.cellRuntime[cellId1] = {
551
+ ...updatedNotebook.cellRuntime[cellId1],
552
+ status: "idle",
553
+ };
554
+ store.set(notebookAtom, updatedNotebook);
555
+ });
556
+
557
+ // 2500 chars sits between MAX_TEXT_OUTPUT_CHARS (2000) and MAX_ERROR_OUTPUT_CHARS (3000)
558
+ const errorContent = "E".repeat(2500);
559
+ vi.mocked(getCellContextData).mockReturnValue({
560
+ cellOutput: {
561
+ outputType: "text",
562
+ processedContent: errorContent,
563
+ imageUrl: null,
564
+ output: {
565
+ mimetype: "application/vnd.marimo+error",
566
+ data: errorContent,
567
+ },
568
+ },
569
+ consoleOutputs: null,
570
+ cellName: "cell1",
571
+ } as never);
572
+
573
+ const result = await tool.handler({}, toolContext as never);
574
+
575
+ const output = result.cellsToOutput?.[cellId1]?.cellOutput ?? "";
576
+ expect(output).not.toContain("[TRUNCATED:");
577
+ expect(output).toContain(errorContent);
578
+ });
579
+
580
+ it("should truncate large console output", async () => {
581
+ const notebook = MockNotebook.notebookState({
582
+ cellData: {
583
+ [cellId1]: { code: 'print("x" * 10000)', edited: true },
584
+ },
585
+ });
586
+ store.set(notebookAtom, notebook);
587
+
588
+ vi.mocked(runCells).mockImplementation(async () => {
589
+ const updatedNotebook = store.get(notebookAtom);
590
+ updatedNotebook.cellRuntime[cellId1] = {
591
+ ...updatedNotebook.cellRuntime[cellId1],
592
+ status: "idle",
593
+ };
594
+ store.set(notebookAtom, updatedNotebook);
595
+ });
596
+
597
+ const largeConsoleText = "x".repeat(10_000);
598
+ vi.mocked(getCellContextData).mockReturnValue({
599
+ cellOutput: null,
600
+ consoleOutputs: [
601
+ {
602
+ outputType: "text",
603
+ processedContent: largeConsoleText,
604
+ imageUrl: null,
605
+ output: { mimetype: "text/plain", data: largeConsoleText },
606
+ },
607
+ ],
608
+ cellName: "cell1",
609
+ } as never);
610
+
611
+ const result = await tool.handler({}, toolContext as never);
612
+
613
+ const consoleOutput =
614
+ result.cellsToOutput?.[cellId1]?.consoleOutput ?? "";
615
+ expect(consoleOutput).toContain("[TRUNCATED:");
616
+ expect(consoleOutput.length).toBeLessThan(2200);
617
+ });
491
618
  });
492
619
 
493
620
  describe("cell execution completion", () => {
@@ -97,7 +97,7 @@ export class RunStaleCellsTool
97
97
 
98
98
  await runCells({
99
99
  cellIds: staleCells,
100
- sendRun: sendRun,
100
+ sendRun,
101
101
  prepareForRun,
102
102
  notebook,
103
103
  });
@@ -121,8 +121,8 @@ export class RunStaleCellsTool
121
121
  const updatedNotebook = store.get(notebookAtom);
122
122
 
123
123
  const cellsToOutput = new Map<CellId, CellOutput | null>();
124
- let resultMessage = "";
125
124
  let outputHasErrors = false;
125
+ let hasAnyConsoleOutput = false;
126
126
  let totalOutputChars = 0;
127
127
 
128
128
  for (const cellId of staleCells) {
@@ -135,12 +135,10 @@ export class RunStaleCellsTool
135
135
  const hasConsoleOutput = consoleOutputs && consoleOutputs.length > 0;
136
136
 
137
137
  // Track errors regardless of budget
138
- if (cellOutput && this.outputHasErrors(cellOutput)) {
139
- outputHasErrors = true;
140
- }
141
138
  if (
142
- hasConsoleOutput &&
143
- consoleOutputs.some((output) => this.outputHasErrors(output))
139
+ (cellOutput && this.outputHasErrors(cellOutput)) ||
140
+ (hasConsoleOutput &&
141
+ consoleOutputs.some((output) => this.outputHasErrors(output)))
144
142
  ) {
145
143
  outputHasErrors = true;
146
144
  }
@@ -167,6 +165,7 @@ export class RunStaleCellsTool
167
165
  }
168
166
 
169
167
  if (hasConsoleOutput) {
168
+ hasAnyConsoleOutput = true;
170
169
  consoleOutputString = consoleOutputs
171
170
  .map((output) => this.formatOutputString(output))
172
171
  .join("\n");
@@ -175,8 +174,6 @@ export class RunStaleCellsTool
175
174
  MAX_TEXT_OUTPUT_CHARS,
176
175
  );
177
176
  totalOutputChars += consoleOutputString.length;
178
- resultMessage +=
179
- "Console output represents the stdout or stderr of the cell (eg. print statements).";
180
177
  }
181
178
 
182
179
  cellsToOutput.set(cellId, {
@@ -202,57 +199,50 @@ export class RunStaleCellsTool
202
199
  return {
203
200
  status: "success",
204
201
  cellsToOutput: Object.fromEntries(cellsToOutput),
205
- message: resultMessage === "" ? undefined : resultMessage,
202
+ message: hasAnyConsoleOutput
203
+ ? "Console output represents the stdout or stderr of the cell (eg. print statements)."
204
+ : undefined,
206
205
  next_steps: nextSteps,
207
206
  };
208
207
  };
209
208
 
210
209
  private outputHasErrors(cellOutput: BaseOutput): boolean {
211
- const { output } = cellOutput;
212
- if (
213
- output.mimetype === "application/vnd.marimo+error" ||
214
- output.mimetype === "application/vnd.marimo+traceback"
215
- ) {
216
- return true;
217
- }
218
- return false;
210
+ return (
211
+ cellOutput.output.mimetype === "application/vnd.marimo+error" ||
212
+ cellOutput.output.mimetype === "application/vnd.marimo+traceback"
213
+ );
219
214
  }
220
215
 
221
216
  private formatOutputString(cellOutput: BaseOutput): string {
222
- let outputString = "";
223
217
  const { outputType, processedContent, imageUrl, output } = cellOutput;
224
- if (outputType === "text") {
225
- if (output.mimetype === "text/html") {
226
- // text/html (e.g. plotly figures, rich dataframes) can be millions of
227
- // chars and is not interpretable by LLMs — summarize instead
228
- const dataLength =
229
- typeof output.data === "string"
230
- ? output.data.length
231
- : JSON.stringify(output.data).length;
232
- outputString += `HTML Output: text/html content (${dataLength.toLocaleString()} chars). Full output visible in notebook UI.`;
233
- } else {
234
- const isError = this.outputHasErrors(cellOutput);
235
- const maxChars = isError
236
- ? MAX_ERROR_OUTPUT_CHARS
237
- : MAX_TEXT_OUTPUT_CHARS;
238
- outputString += "Output:\n";
239
- let content: string;
240
- if (processedContent) {
241
- content = processedContent;
242
- } else if (typeof output.data === "string") {
243
- content = output.data;
244
- } else {
245
- content = JSON.stringify(output.data);
246
- }
247
- outputString += this.truncateString(content, maxChars);
248
- }
249
- } else if (outputType === "media") {
250
- outputString += `Media Output: Contains ${output.mimetype} content`;
251
- if (imageUrl) {
252
- outputString += `\nImage URL: ${imageUrl}`;
253
- }
218
+
219
+ if (outputType === "media") {
220
+ const base = `Media Output: Contains ${output.mimetype} content`;
221
+ return imageUrl ? `${base}\nImage URL: ${imageUrl}` : base;
222
+ }
223
+
224
+ if (output.mimetype === "text/html") {
225
+ // text/html (e.g. plotly figures, rich dataframes) can be millions of
226
+ // chars and is not interpretable by LLMs summarize instead
227
+ const dataLength =
228
+ typeof output.data === "string"
229
+ ? output.data.length
230
+ : JSON.stringify(output.data).length;
231
+ return `HTML Output: text/html content (${dataLength.toLocaleString()} chars). Full output visible in notebook UI.`;
254
232
  }
255
- return outputString;
233
+
234
+ const maxChars = this.outputHasErrors(cellOutput)
235
+ ? MAX_ERROR_OUTPUT_CHARS
236
+ : MAX_TEXT_OUTPUT_CHARS;
237
+
238
+ let content = processedContent;
239
+ if (!content) {
240
+ content =
241
+ typeof output.data === "string"
242
+ ? output.data
243
+ : JSON.stringify(output.data);
244
+ }
245
+ return `Output:\n${this.truncateString(content, maxChars)}`;
256
246
  }
257
247
 
258
248
  private truncateString(str: string, maxLength: number): string {
@@ -261,7 +251,6 @@ export class RunStaleCellsTool
261
251
  }
262
252
  return `${str.slice(0, maxLength)}\n\n[TRUNCATED: ${str.length.toLocaleString()} → ${maxLength.toLocaleString()} chars. Full output visible in the notebook UI.]`;
263
253
  }
264
-
265
254
  /**
266
255
  * Wait for cells to finish executing (status becomes "idle")
267
256
  * Returns true if all cells finished executing, false if the timeout was reached