From e637d1be8eafd70532b5aa2e2333ad709d6acffd Mon Sep 17 00:00:00 2001 From: Brightlyviryaa Date: Mon, 13 Apr 2026 10:19:56 +0700 Subject: [PATCH 1/3] feat(vscode): add image attachment path mode setting --- packages/kilo-vscode/package.json | 9 ++++ packages/kilo-vscode/src/KiloProvider.ts | 36 +++++++++++++ packages/kilo-vscode/webview-ui/src/App.tsx | 13 +++-- .../src/components/chat/PromptInput.tsx | 17 +++++- .../src/context/ImageModeContext.tsx | 49 +++++++++++++++++ .../src/hooks/useImageAttachments.ts | 52 +++++++++++++++---- .../webview-ui/src/types/messages.ts | 27 ++++++++++ 7 files changed, 187 insertions(+), 16 deletions(-) create mode 100644 packages/kilo-vscode/webview-ui/src/context/ImageModeContext.tsx diff --git a/packages/kilo-vscode/package.json b/packages/kilo-vscode/package.json index 9c560530f0c..6494c80020f 100644 --- a/packages/kilo-vscode/package.json +++ b/packages/kilo-vscode/package.json @@ -783,6 +783,15 @@ "type": "boolean", "default": true, "description": "Show the task timeline graph in the chat header" + }, + "kilo-code.new.attachments.imageMode": { + "type": "string", + "default": "data", + "enum": [ + "data", + "path" + ], + "description": "How to send image attachments: 'data' sends base64 data URLs, 'path' sends file:// paths for MCP servers that expect file paths." } } } diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index 0db78a29768..b410d607a53 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -687,6 +687,9 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper case "previewImage": this.handlePreviewImage(message.dataUrl, message.filename) break + case "saveImageToTemp": + await this.handleSaveImageToTemp(message.id, message.mime, message.base64, message.filename) + break case "openFile": if (message.filePath) { this.handleOpenFile(message.filePath, message.line, message.column) @@ -848,6 +851,9 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper case "requestClaudeCompatSetting": this.sendClaudeCompatSetting() break + case "requestImageMode": + this.sendImageMode() + break case "requestNotificationSettings": this.sendNotificationSettings() break @@ -2720,6 +2726,27 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper .then(open, (err) => console.error("[Kilo New] KiloProvider: Failed to preview image:", err)) } + private async handleSaveImageToTemp(id: string, mime: string, base64: string, filename: string): Promise { + const tempDir = vscode.env.tempFilesStoragePath + if (!tempDir) { + console.error("[Kilo New] KiloProvider: tempFilesStoragePath not available") + return + } + + try { + const uri = vscode.Uri.joinPath(vscode.Uri.file(tempDir), `image-${Date.now()}-${filename}`) + const data = Buffer.from(base64, "base64") + await vscode.workspace.fs.writeFile(uri, data) + this.postMessage({ + type: "imageSaved", + id, + filePath: uri.toString(), + }) + } catch (err) { + console.error("[Kilo New] KiloProvider: Failed to save image to temp:", err) + } + } + /** * Handle openFile request from the webview — open a file in the VS Code editor. * Resolves relative paths against the current session's directory (which may be @@ -2830,6 +2857,15 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper }) } + private sendImageMode(): void { + const config = vscode.workspace.getConfiguration("kilo-code.new.attachments") + const mode = config.get<"data" | "path">("imageMode", "data") + this.postMessage({ + type: "imageModeLoaded", + mode, + }) + } + /** Re-fetch all server-side state after an auth change. */ private async reloadAfterAuthChange(): Promise { await Promise.all([ diff --git a/packages/kilo-vscode/webview-ui/src/App.tsx b/packages/kilo-vscode/webview-ui/src/App.tsx index ba72dfc8d8c..b722d0a5d80 100644 --- a/packages/kilo-vscode/webview-ui/src/App.tsx +++ b/packages/kilo-vscode/webview-ui/src/App.tsx @@ -18,6 +18,7 @@ import { ProviderProvider, useProvider } from "./context/provider" import { ConfigProvider } from "./context/config" import { SessionProvider, useSession } from "./context/session" import { LanguageProvider } from "./context/language" +import { ImageModeProvider } from "./context/ImageModeContext" import { ChatView } from "./components/chat" import { MarketplaceView } from "./components/marketplace" import { registerExpandedTaskTool } from "./components/chat/TaskToolExpanded" @@ -285,11 +286,13 @@ const App: Component = () => { - - - - - + + + + + + + diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx index 3b5738cf61d..5b73b7b876b 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx @@ -15,6 +15,7 @@ import { useServer } from "../../context/server" import { useLanguage } from "../../context/language" import { useVSCode } from "../../context/vscode" import { useWorktreeMode } from "../../context/worktree-mode" +import { useImageMode } from "../../context/ImageModeContext" import { ModelSelector } from "../shared/ModelSelector" import { ModeSwitcher } from "../shared/ModeSwitcher" import { ThinkingSelector } from "../shared/ThinkingSelector" @@ -56,11 +57,25 @@ export const PromptInput: Component = (props) => { const language = useLanguage() const vscode = useVSCode() const worktree = useWorktreeMode() + const imageMode = useImageMode() const dialog = useDialog() const mention = useFileMention(vscode) const excluded = worktree ? new Set(["sessions"]) : undefined const slash = useSlashCommand(vscode, excluded) - const imageAttach = useImageAttachments() + const imageAttach = useImageAttachments({ imageMode: imageMode }) + + createEffect(() => { + const mode = imageMode() + imageAttach.replace([]) + }) + + onCleanup( + vscode.onMessage((msg: any) => { + if (msg.type === "imageSaved") { + imageAttach.handleImageSaved(msg.id, msg.filePath) + } + }), + ) imageAttach.setFilePathDropHandler((paths) => { const cwd = server.workspaceDirectory() const resolved = paths.map((p) => convertToMentionPath(p, cwd)) diff --git a/packages/kilo-vscode/webview-ui/src/context/ImageModeContext.tsx b/packages/kilo-vscode/webview-ui/src/context/ImageModeContext.tsx new file mode 100644 index 00000000000..f90a8771c19 --- /dev/null +++ b/packages/kilo-vscode/webview-ui/src/context/ImageModeContext.tsx @@ -0,0 +1,49 @@ +/** + * ImageModeContext + * Provides the current image attachment mode setting ("data" or "path"). + * "data" sends base64 data URLs, "path" sends file:// paths for MCP servers. + */ + +import { createContext, useContext, createSignal, onCleanup, ParentComponent, createEffect } from "solid-js" +import type { Accessor } from "solid-js" +import { useVSCode } from "./vscode" +import type { ExtensionMessage } from "../types/messages" + +export type ImageMode = "data" | "path" + +interface ImageModeContextValue { + imageMode: Accessor +} + +export const ImageModeContext = createContext() + +export const ImageModeProvider: ParentComponent = (props) => { + const vscode = useVSCode() + const [imageMode, setImageMode] = createSignal("data") + + const unsubscribe = vscode.onMessage((message: ExtensionMessage) => { + if (message.type === "imageModeLoaded") { + setImageMode(message.mode) + } + }) + + onCleanup(unsubscribe) + + vscode.postMessage({ type: "requestImageMode" }) + + const value: ImageModeContextValue = { imageMode } + + return {props.children} +} + +export function useImageModeContext(): ImageModeContextValue { + const context = useContext(ImageModeContext) + if (!context) { + throw new Error("useImageModeContext must be used within an ImageModeProvider") + } + return context +} + +export function useImageMode(): Accessor { + return useImageModeContext().imageMode +} diff --git a/packages/kilo-vscode/webview-ui/src/hooks/useImageAttachments.ts b/packages/kilo-vscode/webview-ui/src/hooks/useImageAttachments.ts index b72328b0f3a..58fc49e8f67 100644 --- a/packages/kilo-vscode/webview-ui/src/hooks/useImageAttachments.ts +++ b/packages/kilo-vscode/webview-ui/src/hooks/useImageAttachments.ts @@ -1,4 +1,4 @@ -import { createSignal } from "solid-js" +import { createSignal, type Accessor } from "solid-js" import { ACCEPTED_IMAGE_TYPES, isAcceptedImageType, isDragLeavingComponent } from "./image-attachments-utils" import { extractDropPaths } from "../utils/path-mentions" @@ -12,18 +12,38 @@ export interface ImageAttachment { /** Callback for handling text/URI file path drops. */ export type FilePathDropHandler = (paths: string[]) => void -export function useImageAttachments() { +export interface UseImageAttachmentsOptions { + imageMode?: Accessor<"data" | "path"> +} + +export function useImageAttachments(options: UseImageAttachmentsOptions = {}) { const [images, setImages] = createSignal([]) const [dragging, setDragging] = createSignal(false) let onFilePaths: FilePathDropHandler | undefined - /** Register a handler for file path drops (text/URI-list). */ - const setFilePathDropHandler = (handler: FilePathDropHandler) => { - onFilePaths = handler - } + const getMode = () => options.imageMode?.() ?? "data" const add = (file: File) => { if (!isAcceptedImageType(file.type)) return + const mode = getMode() + if (mode === "path") { + const reader = new FileReader() + reader.onload = () => { + const dataUrl = reader.result as string + const base64 = dataUrl.split(",")[1] + if (!base64) return + const id = crypto.randomUUID() + window.vscode?.postMessage({ + type: "saveImageToTemp", + id, + mime: file.type || "image/png", + base64, + filename: file.name || "image.png", + }) + } + reader.readAsDataURL(file) + return + } const reader = new FileReader() reader.onload = () => { const attachment: ImageAttachment = { @@ -37,6 +57,11 @@ export function useImageAttachments() { reader.readAsDataURL(file) } + /** Register a handler for file path drops (text/URI-list). */ + const setFilePathDropHandler = (handler: FilePathDropHandler) => { + onFilePaths = handler + } + const remove = (id: string) => { setImages((prev) => prev.filter((img) => img.id !== id)) } @@ -59,8 +84,6 @@ export function useImageAttachments() { const handleDragOver = (event: DragEvent) => { const types = event.dataTransfer?.types if (!types) return - // Accept file drops and VS Code URI-list drops (explorer, editor tabs). - // Do NOT accept bare text/plain here — that would intercept normal text drags. const acceptable = types.includes("Files") || types.includes("application/vnd.code.uri-list") if (!acceptable) return event.preventDefault() @@ -79,19 +102,27 @@ export function useImageAttachments() { const dt = event.dataTransfer if (!dt) return - // First: check for text/URI file path drops (VS Code explorer, editor tabs) const paths = extractDropPaths(dt) if (paths && paths.length > 0 && onFilePaths) { onFilePaths(paths) return } - // Second: fall through to image file drops const files = dt.files if (!files) return for (const file of Array.from(files)) add(file) } + const handleImageSaved = (id: string, filePath: string) => { + const attachment: ImageAttachment = { + id, + filename: filePath.split("/").pop() || "image", + mime: "image/png", + dataUrl: `file://${filePath}`, + } + setImages((prev) => [...prev, attachment]) + } + return { images, dragging, @@ -104,5 +135,6 @@ export function useImageAttachments() { handleDragLeave, handleDrop, setFilePathDropHandler, + handleImageSaved, } } diff --git a/packages/kilo-vscode/webview-ui/src/types/messages.ts b/packages/kilo-vscode/webview-ui/src/types/messages.ts index 68429da62c7..e32375729fa 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages.ts @@ -1441,6 +1441,8 @@ export type ExtensionMessage = | QuestionErrorMessage | BrowserSettingsLoadedMessage | ClaudeCompatSettingLoadedMessage + | ImageModeLoadedMessage + | ImageSavedMessage | ConfigLoadedMessage | ConfigUpdatedMessage | GlobalConfigLoadedMessage @@ -1808,6 +1810,21 @@ export interface ClaudeCompatSettingLoadedMessage { enabled: boolean } +export interface RequestImageModeMessage { + type: "requestImageMode" +} + +export interface ImageModeLoadedMessage { + type: "imageModeLoaded" + mode: "data" | "path" +} + +export interface ImageSavedMessage { + type: "imageSaved" + id: string + filePath: string +} + export interface RequestConfigMessage { type: "requestConfig" } @@ -1934,6 +1951,14 @@ export interface RenameWorktreeRequest { label: string } +export interface SaveImageToTempRequest { + type: "saveImageToTemp" + id: string + mime: string + base64: string + filename: string +} + export interface RequestRepoInfoMessage { type: "agentManager.requestRepoInfo" } @@ -2374,6 +2399,7 @@ export type WebviewMessage = | RequestTimelineSettingMessage | RequestBrowserSettingsMessage | RequestClaudeCompatSettingMessage + | RequestImageModeMessage | RequestConfigMessage | RequestGlobalConfigMessage | UpdateConfigMessage @@ -2468,6 +2494,7 @@ export type WebviewMessage = | ToggleSectionCollapsedRequest | MoveToSectionRequest | MoveSectionRequest + | SaveImageToTempRequest // ============================================ // VS Code API type From 7361d2db7b8881b4c9dc09b09a35e02ed6308384 Mon Sep 17 00:00:00 2001 From: Brightlyviryaa Date: Mon, 13 Apr 2026 10:39:48 +0700 Subject: [PATCH 2/3] fix(vscode): address PR review warnings for image attachment path mode - Add onDidChangeConfiguration listener to update webview when setting changes - Pass correct mime type through imageSaved message - Use filePath as-is (uri.toString() already includes file:// prefix) --- packages/kilo-vscode/src/KiloProvider.ts | 8 ++++++++ .../webview-ui/src/components/chat/PromptInput.tsx | 2 +- .../webview-ui/src/context/ImageModeContext.tsx | 3 ++- .../webview-ui/src/hooks/useImageAttachments.ts | 6 +++--- packages/kilo-vscode/webview-ui/src/types/messages.ts | 1 + 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index b410d607a53..b0709a3bd49 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -224,6 +224,13 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper this.slimEditMetadata = options?.slimEditMetadata ?? true TelemetryProxy.getInstance().setProvider(this) + + // Listen for imageMode setting changes and push to webview + vscode.workspace.onDidChangeConfiguration((e) => { + if (e.affectsConfiguration("kilo-code.new.attachments")) { + this.sendImageMode() + } + }) } setRemoteService(service: RemoteStatusService): void { @@ -2741,6 +2748,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper type: "imageSaved", id, filePath: uri.toString(), + mime, }) } catch (err) { console.error("[Kilo New] KiloProvider: Failed to save image to temp:", err) diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx index 5b73b7b876b..80c5702aa9e 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx @@ -72,7 +72,7 @@ export const PromptInput: Component = (props) => { onCleanup( vscode.onMessage((msg: any) => { if (msg.type === "imageSaved") { - imageAttach.handleImageSaved(msg.id, msg.filePath) + imageAttach.handleImageSaved(msg.id, msg.filePath, msg.mime) } }), ) diff --git a/packages/kilo-vscode/webview-ui/src/context/ImageModeContext.tsx b/packages/kilo-vscode/webview-ui/src/context/ImageModeContext.tsx index f90a8771c19..126f28d03ae 100644 --- a/packages/kilo-vscode/webview-ui/src/context/ImageModeContext.tsx +++ b/packages/kilo-vscode/webview-ui/src/context/ImageModeContext.tsx @@ -4,7 +4,7 @@ * "data" sends base64 data URLs, "path" sends file:// paths for MCP servers. */ -import { createContext, useContext, createSignal, onCleanup, ParentComponent, createEffect } from "solid-js" +import { createContext, useContext, createSignal, onCleanup, ParentComponent } from "solid-js" import type { Accessor } from "solid-js" import { useVSCode } from "./vscode" import type { ExtensionMessage } from "../types/messages" @@ -29,6 +29,7 @@ export const ImageModeProvider: ParentComponent = (props) => { onCleanup(unsubscribe) + // Request initial value and listen for changes vscode.postMessage({ type: "requestImageMode" }) const value: ImageModeContextValue = { imageMode } diff --git a/packages/kilo-vscode/webview-ui/src/hooks/useImageAttachments.ts b/packages/kilo-vscode/webview-ui/src/hooks/useImageAttachments.ts index 58fc49e8f67..e025fbfea7d 100644 --- a/packages/kilo-vscode/webview-ui/src/hooks/useImageAttachments.ts +++ b/packages/kilo-vscode/webview-ui/src/hooks/useImageAttachments.ts @@ -113,12 +113,12 @@ export function useImageAttachments(options: UseImageAttachmentsOptions = {}) { for (const file of Array.from(files)) add(file) } - const handleImageSaved = (id: string, filePath: string) => { + const handleImageSaved = (id: string, filePath: string, mime: string) => { const attachment: ImageAttachment = { id, filename: filePath.split("/").pop() || "image", - mime: "image/png", - dataUrl: `file://${filePath}`, + mime: mime || "image/png", + dataUrl: filePath, } setImages((prev) => [...prev, attachment]) } diff --git a/packages/kilo-vscode/webview-ui/src/types/messages.ts b/packages/kilo-vscode/webview-ui/src/types/messages.ts index e32375729fa..16140654c90 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages.ts @@ -1823,6 +1823,7 @@ export interface ImageSavedMessage { type: "imageSaved" id: string filePath: string + mime: string } export interface RequestConfigMessage { From fa3e55fdca9710653630c5024df01688b5f1d948 Mon Sep 17 00:00:00 2001 From: Brightlyviryaa Date: Mon, 13 Apr 2026 10:45:28 +0700 Subject: [PATCH 3/3] fix(vscode): dispose configChangeDisposable on provider dispose --- packages/kilo-vscode/src/KiloProvider.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index b0709a3bd49..31c02fd99e3 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -186,6 +186,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper private webviewMessageDisposable: vscode.Disposable | null = null private viewStateDisposable: vscode.Disposable | null = null private visibilityDisposable: vscode.Disposable | null = null + private configChangeDisposable: vscode.Disposable | null = null /** Lazily initialized ignore controller for .kilocodeignore filtering */ private ignoreController: FileIgnoreController | null = null @@ -226,7 +227,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper TelemetryProxy.getInstance().setProvider(this) // Listen for imageMode setting changes and push to webview - vscode.workspace.onDidChangeConfiguration((e) => { + this.configChangeDisposable = vscode.workspace.onDidChangeConfiguration((e) => { if (e.affectsConfiguration("kilo-code.new.attachments")) { this.sendImageMode() } @@ -3330,6 +3331,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper this.viewStateDisposable?.dispose() this.visibilityDisposable?.dispose() this.webviewMessageDisposable?.dispose() + this.configChangeDisposable?.dispose() this.isWebviewReady = false this.promptRecoveryQueued = false clearNetworkWaits(this.trackedSessionIds)