Skip to content

Conversation

@rebornix
Copy link
Member

No description provided.

@rebornix rebornix self-assigned this Oct 23, 2025
@rebornix rebornix requested a review from DonJayamanne November 8, 2025 19:00
@rebornix rebornix marked this pull request as ready for review November 8, 2025 19:00
Copilot AI review requested due to automatic review settings November 8, 2025 19:00
@rebornix
Copy link
Member Author

rebornix commented Nov 8, 2025

From the logs I can see the images are attached, but when the image size is large, it gets dropped due to

2025-11-08 10:59:39.546 [debug] Checking if image is valid size and dimensions. Max size: 3145728, Max dimension: 2000
2025-11-08 10:59:39.546 [debug] Image x or y dimension exceeds the maximum limit of 2000, attempting to resize
2025-11-08 10:59:39.546 [error] Failed to resize image for upload: A boolean was expected
2025-11-08 10:59:39.546 [debug] Failed to resize image to fit within the maximum dimensions limits, skipping
2025-11-08 10:59:39.546 [error] Failed to process image /Users/penlv/Library/Application Support/Code - Insiders/User/globalStorage/github.copilot-chat/copilot-cli-images/1762628379315-w37lxrbt.png: Image too large or couldn't be processed: /Users/penlv/Library/Application Support/Code - Insiders/User/globalStorage/github.copilot-chat/copilot-cli-images/1762628379315-w37lxrbt.png

@rebornix
Copy link
Member Author

rebornix commented Nov 8, 2025

When image size is proper, the next error I got is

❌ Error: (model_call) {"message":"missing required Copilot-Vision-Request header for vision requests","code":""}

@vs-code-engineering vs-code-engineering bot added this to the November 2025 milestone Nov 8, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds image attachment support to the GitHub Copilot CLI chat participant. The implementation introduces a new ImageStorage class for managing uploaded images and updates the prompt resolver to process ChatReferenceBinaryData references.

Key Changes

  • New image storage system that saves images to global storage with automatic cleanup of old files
  • Enhanced prompt resolver to handle binary data references alongside existing file and diagnostic references
  • Updated participant capabilities to advertise image attachment support

Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/extension/chatSessions/vscode-node/copilotCLIImageSupport.ts New file implementing image storage with persistent file management and cleanup logic
src/extension/chatSessions/vscode-node/copilotCLIPromptResolver.ts Added image handling logic and refactored reference processing to use parallel execution
src/extension/chatSessions/vscode-node/copilotCLIChatSessionsContribution.ts Updated import path for CopilotCLIPromptResolver from agents directory to local chatSessions directory
src/extension/chatSessions/vscode-node/chatSessions.ts Updated import path for CopilotCLIPromptResolver to reflect new location
package.json Added supportsImageAttachments: true capability to copilotcli chat session configuration
Comments suppressed due to low confidence (3)

src/extension/chatSessions/vscode-node/copilotCLIPromptResolver.ts:123

  • The diagnosticTexts and allRefsTexts arrays are being concurrently modified by multiple promises within Promise.all(). While JavaScript arrays support concurrent push operations, this creates non-deterministic ordering of diagnostic and reference texts. Since these are displayed to users in the prompt, the inconsistent ordering could make the output confusing or harder to follow across different invocations with the same inputs.
    src/extension/chatSessions/vscode-node/copilotCLIPromptResolver.ts:26
  • The image storage initialization happens synchronously in the constructor via new ImageStorage(extensionContext), but ImageStorage.initialize() is async and not awaited. This means when storeImage() is called on line 47, the storage directory may not exist yet, potentially causing the image storage operation to fail.
    src/extension/chatSessions/vscode-node/copilotCLIPromptResolver.ts:88
  • When a file has multiple diagnostics, the same file attachment is added to the attachments array multiple times (once per diagnostic). This creates duplicate attachments in the output. Consider collecting unique file URIs and adding each file only once, or implementing deduplication logic before returning the attachments.

Comment on lines +10 to +25
export class ImageStorage {
private readonly storageDir: URI;

constructor(private readonly context: IVSCodeExtensionContext) {
this.storageDir = URI.joinPath(this.context.globalStorageUri, 'copilot-cli-images');
this.initialize();
}

private async initialize(): Promise<void> {
try {
await vscode.workspace.fs.createDirectory(this.storageDir);
await this.cleanupOldImages();
} catch (error) {
console.error('ImageStorage: Failed to initialize', error);
}
}
Copy link

Copilot AI Nov 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ImageStorage class is directly using vscode.workspace.fs and console.error instead of the proper service abstractions. According to the coding guidelines:

  • Use IFileService instead of vscode.workspace.fs for file operations
  • Use ILogService instead of console.error for logging

The class should inject these services in the constructor to maintain consistency with the project's service-oriented architecture.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +13 to +17
constructor(private readonly context: IVSCodeExtensionContext) {
this.storageDir = URI.joinPath(this.context.globalStorageUri, 'copilot-cli-images');
this.initialize();
}

Copy link

Copilot AI Nov 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The initialize() method is called in the constructor but is async and not awaited. This means the storage directory creation and cleanup may not complete before storeImage() is called, potentially causing file operation failures. Consider making initialization synchronous or ensuring it completes before allowing image storage operations.

Suggested change
constructor(private readonly context: IVSCodeExtensionContext) {
this.storageDir = URI.joinPath(this.context.globalStorageUri, 'copilot-cli-images');
this.initialize();
}
private constructor(private readonly context: IVSCodeExtensionContext) {
this.storageDir = URI.joinPath(this.context.globalStorageUri, 'copilot-cli-images');
}
static async create(context: IVSCodeExtensionContext): Promise<ImageStorage> {
const instance = new ImageStorage(context);
await instance.initialize();
return instance;
}

Copilot uses AI. Check for mistakes.
Comment on lines +23 to +75
console.error('ImageStorage: Failed to initialize', error);
}
}

async storeImage(imageData: Uint8Array, mimeType: string): Promise<URI> {
const timestamp = Date.now();
const randomId = Math.random().toString(36).substring(2, 10);
const extension = this.getExtension(mimeType);
const filename = `${timestamp}-${randomId}${extension}`;
const imageUri = URI.joinPath(this.storageDir, filename);

await vscode.workspace.fs.writeFile(imageUri, imageData);
return imageUri;
}

async getImage(uri: URI): Promise<Uint8Array | undefined> {
try {
const data = await vscode.workspace.fs.readFile(uri);
return data;
} catch {
return undefined;
}
}

async deleteImage(uri: URI): Promise<void> {
try {
await vscode.workspace.fs.delete(uri);
} catch {
// Already deleted
}
}

async cleanupOldImages(maxAgeMs: number = 7 * 24 * 60 * 60 * 1000): Promise<void> {
try {
const entries = await vscode.workspace.fs.readDirectory(this.storageDir);
const now = Date.now();
const cutoff = now - maxAgeMs;

for (const [filename, fileType] of entries) {
if (fileType === vscode.FileType.File) {
const fileUri = URI.joinPath(this.storageDir, filename);
try {
const stat = await vscode.workspace.fs.stat(fileUri);
if (stat.mtime < cutoff) {
await vscode.workspace.fs.delete(fileUri);
}
} catch {
// Skip files we can't access
}
}
}
} catch (error) {
console.error('ImageStorage: Failed to cleanup old images', error);
Copy link

Copilot AI Nov 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using console.error for logging violates the coding guideline to use ILogService instead. All error logging should be done through the injected log service to maintain consistency and enable proper log management.

Copilot generated this review using guidance from repository custom instructions.
try {
const stat = await vscode.workspace.fs.stat(fileUri);
if (stat.mtime < cutoff) {
await vscode.workspace.fs.delete(fileUri);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be more efficient to do a Promise.all(..)
But thats fine, can change after this is merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants