diff --git a/src/extension/agents/copilotcli/node/copilotcliPromptResolver.ts b/src/extension/agents/copilotcli/node/copilotcliPromptResolver.ts index 42ce4cd0ef..19f11fd67d 100644 --- a/src/extension/agents/copilotcli/node/copilotcliPromptResolver.ts +++ b/src/extension/agents/copilotcli/node/copilotcliPromptResolver.ts @@ -18,6 +18,9 @@ import { ChatReferenceBinaryData, FileType } from '../../../../vscodeTypes'; import { ChatVariablesCollection, isPromptFile, isPromptInstruction } from '../../../prompt/common/chatVariablesCollection'; import { generateUserPrompt } from '../../../prompts/node/agent/copilotCLIPrompt'; +// Use dynamic import to work around ESLint restrictions +const vscodeRuntime = import('vscode'); + export class CopilotCLIPromptResolver { constructor( @ILogService private readonly logService: ILogService, @@ -30,17 +33,25 @@ export class CopilotCLIPromptResolver { if (request.prompt.startsWith('/')) { return { prompt: request.prompt, attachments: [] }; // likely a slash command, don't modify } - const [variables, attachments] = await this.constructChatVariablesAndAttachments(new ChatVariablesCollection(request.references), token); + const [variables, attachments, untitledFilesContext] = await this.constructChatVariablesAndAttachments(new ChatVariablesCollection(request.references), token); if (token.isCancellationRequested) { return { prompt: request.prompt, attachments: [] }; } - const prompt = await raceCancellation(generateUserPrompt(request, variables, this.instantiationService), token); + let prompt = await raceCancellation(generateUserPrompt(request, variables, this.instantiationService), token); + + // Append untitled file context to the prompt if present + if (untitledFilesContext && prompt) { + prompt = `${prompt}\n\n${untitledFilesContext}`; + } + return { prompt: prompt ?? '', attachments }; } - private async constructChatVariablesAndAttachments(variables: ChatVariablesCollection, token: vscode.CancellationToken): Promise<[variables: ChatVariablesCollection, Attachment[]]> { + private async constructChatVariablesAndAttachments(variables: ChatVariablesCollection, token: vscode.CancellationToken): Promise<[variables: ChatVariablesCollection, Attachment[], untitledFilesContext: string | undefined]> { const validReferences: vscode.ChatPromptReference[] = []; const fileFolderReferences: vscode.ChatPromptReference[] = []; + const untitledFileReferences: vscode.ChatPromptReference[] = []; + for (const variable of variables) { // Unsupported references. if (isPromptInstruction(variable) || isPromptFile(variable)) { @@ -60,6 +71,13 @@ export class CopilotCLIPromptResolver { continue; } + // Handle untitled files separately + if (variable.value.scheme === 'untitled') { + validReferences.push(variable.reference); + untitledFileReferences.push(variable.reference); + continue; + } + // Files and directories will be attached using regular attachments via Copilot CLI SDK. validReferences.push(variable.reference); fileFolderReferences.push(variable.reference); @@ -71,7 +89,8 @@ export class CopilotCLIPromptResolver { variables = new ChatVariablesCollection(validReferences); const attachments = await this.constructFileOrFolderAttachments(fileFolderReferences, token); - return [variables, attachments]; + const untitledFilesContext = await this.extractUntitledFileContent(untitledFileReferences, token); + return [variables, attachments, untitledFilesContext]; } @@ -82,6 +101,12 @@ export class CopilotCLIPromptResolver { if (!URI.isUri(uri)) { return; } + + // Skip untitled files - they will be handled in constructChatVariablesAndAttachments + if (uri.scheme === 'untitled') { + return; + } + if (await this.ignoreService.isCopilotIgnored(uri)) { return; } @@ -108,4 +133,35 @@ export class CopilotCLIPromptResolver { return attachments; } + + private async extractUntitledFileContent(untitledFileReferences: vscode.ChatPromptReference[], token: vscode.CancellationToken): Promise { + if (!untitledFileReferences || untitledFileReferences.length === 0) { + return undefined; + } + + const fullFileParts: string[] = []; + const vscodeMod = await vscodeRuntime; + + for (const ref of untitledFileReferences) { + if (!URI.isUri(ref.value) || ref.value.scheme !== 'untitled') { + continue; + } + + try { + const document = await vscodeMod.workspace.openTextDocument(ref.value); + const content = document.getText(); + fullFileParts.push(`${ref.value.path}`); + fullFileParts.push(content); + fullFileParts.push(`${ref.value.path}`); + } catch (error) { + this.logService.error(`Error reading untitled file content for reference: ${ref.value.toString()}: ${error}`); + } + } + + if (fullFileParts.length === 0) { + return undefined; + } + + return ['The user has attached the following untitled files as relevant context:', ...fullFileParts].join('\n'); + } }