From 97a38bf65217d89619b3386c620333a97ee391b7 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Wed, 22 Apr 2026 00:56:01 +0200 Subject: [PATCH] fix(coding-agent): configure inline tool image width closes #3508 --- packages/coding-agent/CHANGELOG.md | 1 + packages/coding-agent/docs/settings.md | 1 + .../coding-agent/src/core/settings-manager.ts | 18 ++++++++++++++++++ .../components/settings-selector.ts | 14 +++++++++++++- .../interactive/components/tool-execution.ts | 10 +++++++++- .../src/modes/interactive/interactive-mode.ts | 16 +++++++++++++++- 6 files changed, 57 insertions(+), 3 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index d6ab7f14..2aa38267 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -4,6 +4,7 @@ ### Fixed +- Added configurable interactive inline image width via `terminal.imageWidthCells` and `/settings`, so tool-output images are no longer hard-capped to 60 terminal cells ([#3508](https://github.com/badlogic/pi-mono/issues/3508)) - Fixed `sessionDir` in `settings.json` to expand `~`, so portable session-directory settings no longer require a shell wrapper ([#3514](https://github.com/badlogic/pi-mono/issues/3514)) - Fixed parallel tool-call rows to leave the pending state as soon as each tool is finalized, while still appending persisted tool results in assistant source order ([#3503](https://github.com/badlogic/pi-mono/issues/3503)) - Fixed exported session markdown to render Markdown while showing HTML-like message content such as `...` verbatim, so shared sessions match the TUI instead of letting the browser interpret message text ([#3484](https://github.com/badlogic/pi-mono/issues/3484)) diff --git a/packages/coding-agent/docs/settings.md b/packages/coding-agent/docs/settings.md index 563c3aa3..d745edef 100644 --- a/packages/coding-agent/docs/settings.md +++ b/packages/coding-agent/docs/settings.md @@ -108,6 +108,7 @@ When a provider requests a retry delay longer than `maxDelayMs` (e.g., Google's | Setting | Type | Default | Description | |---------|------|---------|-------------| | `terminal.showImages` | boolean | `true` | Show images in terminal (if supported) | +| `terminal.imageWidthCells` | number | `60` | Preferred inline image width in terminal cells | | `terminal.clearOnShrink` | boolean | `false` | Clear empty rows when content shrinks (can cause flicker) | | `images.autoResize` | boolean | `true` | Resize images to 2000x2000 max | | `images.blockImages` | boolean | `false` | Block all images from being sent to LLM | diff --git a/packages/coding-agent/src/core/settings-manager.ts b/packages/coding-agent/src/core/settings-manager.ts index fb923770..6e8835ef 100644 --- a/packages/coding-agent/src/core/settings-manager.ts +++ b/packages/coding-agent/src/core/settings-manager.ts @@ -25,6 +25,7 @@ export interface RetrySettings { export interface TerminalSettings { showImages?: boolean; // default: true (only relevant if terminal supports images) + imageWidthCells?: number; // default: 60 (preferred inline image width in terminal cells) clearOnShrink?: boolean; // default: false (clear empty rows when content shrinks) } @@ -870,6 +871,23 @@ export class SettingsManager { this.save(); } + getImageWidthCells(): number { + const width = this.settings.terminal?.imageWidthCells; + if (typeof width !== "number" || !Number.isFinite(width)) { + return 60; + } + return Math.max(1, Math.floor(width)); + } + + setImageWidthCells(width: number): void { + if (!this.globalSettings.terminal) { + this.globalSettings.terminal = {}; + } + this.globalSettings.terminal.imageWidthCells = Math.max(1, Math.floor(width)); + this.markModified("terminal", "imageWidthCells"); + this.save(); + } + getClearOnShrink(): boolean { // Settings takes precedence, then env var, then default false if (this.settings.terminal?.clearOnShrink !== undefined) { diff --git a/packages/coding-agent/src/modes/interactive/components/settings-selector.ts b/packages/coding-agent/src/modes/interactive/components/settings-selector.ts index cf3473e2..518e0095 100644 --- a/packages/coding-agent/src/modes/interactive/components/settings-selector.ts +++ b/packages/coding-agent/src/modes/interactive/components/settings-selector.ts @@ -31,6 +31,7 @@ const THINKING_DESCRIPTIONS: Record = { export interface SettingsConfig { autoCompact: boolean; showImages: boolean; + imageWidthCells: number; autoResizeImages: boolean; blockImages: boolean; enableSkillCommands: boolean; @@ -56,6 +57,7 @@ export interface SettingsConfig { export interface SettingsCallbacks { onAutoCompactChange: (enabled: boolean) => void; onShowImagesChange: (enabled: boolean) => void; + onImageWidthCellsChange: (width: number) => void; onAutoResizeImagesChange: (enabled: boolean) => void; onBlockImagesChange: (blocked: boolean) => void; onEnableSkillCommandsChange: (enabled: boolean) => void; @@ -292,10 +294,17 @@ export class SettingsSelectorComponent extends Container { currentValue: config.showImages ? "true" : "false", values: ["true", "false"], }); + items.splice(2, 0, { + id: "image-width-cells", + label: "Image width", + description: "Preferred inline image width in terminal cells", + currentValue: String(config.imageWidthCells), + values: ["60", "80", "120"], + }); } // Image auto-resize toggle (always available, affects both attached and read images) - items.splice(supportsImages ? 2 : 1, 0, { + items.splice(supportsImages ? 3 : 1, 0, { id: "auto-resize-images", label: "Auto-resize images", description: "Resize large images to 2000x2000 max for better model compatibility", @@ -378,6 +387,9 @@ export class SettingsSelectorComponent extends Container { case "show-images": callbacks.onShowImagesChange(newValue === "true"); break; + case "image-width-cells": + callbacks.onImageWidthCellsChange(parseInt(newValue, 10)); + break; case "auto-resize-images": callbacks.onAutoResizeImagesChange(newValue === "true"); break; diff --git a/packages/coding-agent/src/modes/interactive/components/tool-execution.ts b/packages/coding-agent/src/modes/interactive/components/tool-execution.ts index c6ed2374..864a6938 100644 --- a/packages/coding-agent/src/modes/interactive/components/tool-execution.ts +++ b/packages/coding-agent/src/modes/interactive/components/tool-execution.ts @@ -7,6 +7,7 @@ import { theme } from "../theme/theme.js"; export interface ToolExecutionOptions { showImages?: boolean; + imageWidthCells?: number; } export class ToolExecutionComponent extends Container { @@ -23,6 +24,7 @@ export class ToolExecutionComponent extends Container { private args: any; private expanded = false; private showImages: boolean; + private imageWidthCells: number; private isPartial = true; private toolDefinition?: ToolDefinition; private builtInToolDefinition?: ToolDefinition; @@ -54,6 +56,7 @@ export class ToolExecutionComponent extends Container { this.toolDefinition = toolDefinition; this.builtInToolDefinition = createAllToolDefinitions(cwd)[toolName as ToolName]; this.showImages = options.showImages ?? true; + this.imageWidthCells = options.imageWidthCells ?? 60; this.ui = ui; this.cwd = cwd; @@ -205,6 +208,11 @@ export class ToolExecutionComponent extends Container { this.updateDisplay(); } + setImageWidthCells(width: number): void { + this.imageWidthCells = Math.max(1, Math.floor(width)); + this.updateDisplay(); + } + override invalidate(): void { super.invalidate(); this.updateDisplay(); @@ -312,7 +320,7 @@ export class ToolExecutionComponent extends Container { imageData, imageMimeType, { fallbackColor: (s: string) => theme.fg("toolOutput", s) }, - { maxWidthCells: 60 }, + { maxWidthCells: this.imageWidthCells }, ); this.imageComponents.push(imageComponent); this.addChild(imageComponent); diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index 8e678d7e..0c218d4f 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -2633,6 +2633,7 @@ export class InteractiveMode { content.arguments, { showImages: this.settingsManager.getShowImages(), + imageWidthCells: this.settingsManager.getImageWidthCells(), }, this.getRegisteredToolDefinition(content.name), this.ui, @@ -2701,6 +2702,7 @@ export class InteractiveMode { event.args, { showImages: this.settingsManager.getShowImages(), + imageWidthCells: this.settingsManager.getImageWidthCells(), }, this.getRegisteredToolDefinition(event.toolName), this.ui, @@ -3031,7 +3033,10 @@ export class InteractiveMode { content.name, content.id, content.arguments, - { showImages: this.settingsManager.getShowImages() }, + { + showImages: this.settingsManager.getShowImages(), + imageWidthCells: this.settingsManager.getImageWidthCells(), + }, this.getRegisteredToolDefinition(content.name), this.ui, this.sessionManager.getCwd(), @@ -3650,6 +3655,7 @@ export class InteractiveMode { { autoCompact: this.session.autoCompactionEnabled, showImages: this.settingsManager.getShowImages(), + imageWidthCells: this.settingsManager.getImageWidthCells(), autoResizeImages: this.settingsManager.getImageAutoResize(), blockImages: this.settingsManager.getBlockImages(), enableSkillCommands: this.settingsManager.getEnableSkillCommands(), @@ -3684,6 +3690,14 @@ export class InteractiveMode { } } }, + onImageWidthCellsChange: (width) => { + this.settingsManager.setImageWidthCells(width); + for (const child of this.chatContainer.children) { + if (child instanceof ToolExecutionComponent) { + child.setImageWidthCells(width); + } + } + }, onAutoResizeImagesChange: (enabled) => { this.settingsManager.setImageAutoResize(enabled); },