Path: blob/main/extensions/copilot/src/extension/chatSessions/copilotcli/node/copilotCLIImageSupport.ts
13405 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as fs from 'fs/promises';6import { IVSCodeExtensionContext } from '../../../../platform/extContext/common/extensionContext';7import { createDirectoryIfNotExists, IFileSystemService } from '../../../../platform/filesystem/common/fileSystemService';8import { ILogService } from '../../../../platform/log/common/logService';9import { createServiceIdentifier } from '../../../../util/common/services';10import { Lazy } from '../../../../util/vs/base/common/lazy';11import { ResourceSet } from '../../../../util/vs/base/common/map';12import { URI } from '../../../../util/vs/base/common/uri';1314export interface ICopilotCLIImageSupport {15readonly _serviceBrand: undefined;16storeImage(imageData: Uint8Array, mimeType: string): Promise<URI>;17isTrustedImage(imageUri: URI): boolean;18}1920export function isImageMimeType(mimeType: string): boolean {21const map: Record<string, string> = {22'image/png': '.png',23'image/jpeg': '.jpg',24'image/jpg': '.jpg',25'image/gif': '.gif',26'image/webp': '.webp',27'image/bmp': '.bmp',28};29return mimeType.toLowerCase() in map;30}3132export const ICopilotCLIImageSupport = createServiceIdentifier<ICopilotCLIImageSupport>('ICopilotCLIImageSupport');3334export class CopilotCLIImageSupport implements ICopilotCLIImageSupport {35readonly _serviceBrand: undefined;36private readonly storageDir: URI;37private readonly initialized: Lazy<Promise<void>>;38private readonly trustedImages = new ResourceSet();39constructor(40@IVSCodeExtensionContext private readonly context: IVSCodeExtensionContext,41@ILogService private readonly logService: ILogService,42@IFileSystemService private readonly fileSystemService: IFileSystemService,43) {44this.storageDir = URI.joinPath(this.context.globalStorageUri, 'copilot-cli-images');45this.initialized = new Lazy<Promise<void>>(() => this.initialize());46void this.initialized.value;47}4849private async initialize(): Promise<void> {50try {51await createDirectoryIfNotExists(this.fileSystemService, this.storageDir);52void this.cleanupOldImages();53} catch (error) {54this.logService.error(`[CopilotCLISession] ImageStorage: Failed to initialize`, error);55}56}5758isTrustedImage(imageUri: URI): boolean {59return this.trustedImages.has(imageUri);60}6162async storeImage(imageData: Uint8Array, mimeType: string): Promise<URI> {63await this.initialized.value;64const timestamp = Date.now();65const randomId = Math.random().toString(36).substring(2, 10);66const extension = this.getExtension(mimeType);67const filename = `${timestamp}-${randomId}${extension}`;68const imageUri = URI.file(URI.joinPath(this.storageDir, filename).fsPath);6970await fs.writeFile(imageUri.fsPath, imageData);71this.trustedImages.add(imageUri);72return imageUri;73}7475async cleanupOldImages(maxAgeMs: number = 7 * 24 * 60 * 60 * 1000): Promise<void> {76try {77const entries = await fs.readdir(this.storageDir.fsPath, { withFileTypes: true });78const now = Date.now();79const cutoff = now - maxAgeMs;8081for (const entry of entries) {82if (entry.isFile()) {83const fileUri = URI.joinPath(this.storageDir, entry.name);84try {85const stat = await fs.stat(fileUri.fsPath);86if (stat.mtime.getTime() < cutoff) {87await fs.unlink(fileUri.fsPath);88}89} catch {90// Skip files we can't access91}92}93}94} catch (error) {95console.error('ImageStorage: Failed to cleanup old images', error);96}97}9899private getExtension(mimeType: string): string {100const map: Record<string, string> = {101'image/png': '.png',102'image/jpeg': '.jpg',103'image/jpg': '.jpg',104'image/gif': '.gif',105'image/webp': '.webp',106'image/bmp': '.bmp',107};108return map[mimeType.toLowerCase()] || '.bin';109}110}111112113