fix(tui): render xdev tool images inline
This commit is contained in:
@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed images returned by tools mounted under `xd://` rendering only as file links instead of inline terminal graphics.
|
||||
|
||||
## [17.3.7] - 2026-08-17
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
Text,
|
||||
type TUI,
|
||||
} from "@oh-my-pi/pi-tui";
|
||||
import { getProjectDir, logger, sanitizeText } from "@oh-my-pi/pi-utils";
|
||||
import { getProjectDir, isRecord, logger, sanitizeText } from "@oh-my-pi/pi-utils";
|
||||
import { EDIT_MODE_STRATEGIES, type EditMode, type PerFileDiffPreview } from "../../edit";
|
||||
import type { Theme } from "../../modes/theme/theme";
|
||||
import { getThemeEpoch, theme } from "../../modes/theme/theme";
|
||||
@@ -73,6 +73,21 @@ function isTodoToolDetails(details: unknown): details is TodoToolDetails {
|
||||
);
|
||||
}
|
||||
|
||||
interface ToolImageBlock {
|
||||
data?: string;
|
||||
mimeType?: string;
|
||||
}
|
||||
|
||||
function imageBlocksFromDetails(details: unknown): ToolImageBlock[] {
|
||||
if (!isRecord(details) || !Array.isArray(details.images)) return [];
|
||||
return details.images.filter(
|
||||
(image): image is ToolImageBlock =>
|
||||
isRecord(image) &&
|
||||
(image.data === undefined || typeof image.data === "string") &&
|
||||
(image.mimeType === undefined || typeof image.mimeType === "string"),
|
||||
);
|
||||
}
|
||||
|
||||
function displaceableToolName(
|
||||
toolName: string,
|
||||
result: { details?: unknown; isError?: boolean },
|
||||
@@ -617,14 +632,18 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all image blocks from result content and details.images.
|
||||
* Some tools (like generate_image) store images in details to avoid bloating model context.
|
||||
* Get all image blocks from result content and details.
|
||||
* Some tools (like generate_image) store images in details to avoid bloating
|
||||
* model context. Xdev-dispatched tools preserve those details under
|
||||
* details.xdev.inner.
|
||||
*/
|
||||
#getAllImageBlocks(): Array<{ data?: string; mimeType?: string }> {
|
||||
#getAllImageBlocks(): ToolImageBlock[] {
|
||||
if (!this.#result) return [];
|
||||
const contentImages = this.#result.content?.filter((c: any) => c.type === "image") || [];
|
||||
const detailImages = this.#result.details?.images || [];
|
||||
return [...contentImages, ...detailImages];
|
||||
const contentImages = this.#result.content.filter(block => block.type === "image");
|
||||
const details = this.#result.details;
|
||||
const detailImages = imageBlocksFromDetails(details);
|
||||
const xdevImages = isRecord(details) && isRecord(details.xdev) ? imageBlocksFromDetails(details.xdev.inner) : [];
|
||||
return [...contentImages, ...detailImages, ...xdevImages];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,7 +12,7 @@ const IMAGE: ImageContent = {
|
||||
};
|
||||
const originalProtocol = TERMINAL.imageProtocol;
|
||||
|
||||
describe("Kitty non-PNG conversion failures", () => {
|
||||
describe("Tool image rendering", () => {
|
||||
beforeAll(async () => {
|
||||
await initTheme();
|
||||
});
|
||||
@@ -48,4 +48,36 @@ describe("Kitty non-PNG conversion failures", () => {
|
||||
component.updateResult({ content: [IMAGE] }, false);
|
||||
expect(requestRender).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("surfaces images returned through xdev write results", () => {
|
||||
const component = new ToolExecutionComponent(
|
||||
"write",
|
||||
{ path: "xd://generate_image" },
|
||||
{ showImages: true },
|
||||
undefined,
|
||||
{
|
||||
requestRender: vi.fn(),
|
||||
requestComponentRender: vi.fn(),
|
||||
resetDisplay: vi.fn(),
|
||||
},
|
||||
);
|
||||
|
||||
component.updateResult(
|
||||
{
|
||||
content: [{ type: "text", text: "Generated 1 image" }],
|
||||
details: {
|
||||
xdev: {
|
||||
tool: "generate_image",
|
||||
mode: "execute",
|
||||
inner: {
|
||||
images: [{ data: IMAGE.data, mimeType: "image/png" }],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
expect(component.render(80).join("\n")).toContain("\x1b_G");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user