diff --git a/src/extension/chatSessions/vscode-node/copilotCLIChatSessionsContribution.ts b/src/extension/chatSessions/vscode-node/copilotCLIChatSessionsContribution.ts index 00e57e9602..d3a00c3977 100644 --- a/src/extension/chatSessions/vscode-node/copilotCLIChatSessionsContribution.ts +++ b/src/extension/chatSessions/vscode-node/copilotCLIChatSessionsContribution.ts @@ -13,9 +13,8 @@ import { IVSCodeExtensionContext } from '../../../platform/extContext/common/ext import { IGitService } from '../../../platform/git/common/gitService'; import { toGitUri } from '../../../platform/git/common/utils'; import { ILogService } from '../../../platform/log/common/logService'; -import { ParsedPromptFile, PromptFileParser } from '../../../platform/promptFiles/common/promptsService'; +import { IPromptsService, ParsedPromptFile } from '../../../platform/promptFiles/common/promptsService'; import { ITelemetryService } from '../../../platform/telemetry/common/telemetry'; -import { IWorkspaceService } from '../../../platform/workspace/common/workspaceService'; import { disposableTimeout } from '../../../util/vs/base/common/async'; import { isCancellationError } from '../../../util/vs/base/common/errors'; import { Emitter, Event } from '../../../util/vs/base/common/event'; @@ -414,7 +413,7 @@ export class CopilotCLIChatSessionParticipant extends Disposable { @IConfigurationService private readonly configurationService: IConfigurationService, @ICopilotCLISDK private readonly copilotCLISDK: ICopilotCLISDK, @ILogService private readonly logService: ILogService, - @IWorkspaceService private readonly workspaceService: IWorkspaceService, + @IPromptsService private readonly promptsService: IPromptsService, ) { super(); } @@ -458,8 +457,8 @@ export class CopilotCLIChatSessionParticipant extends Disposable { const additionalReferences = this.previousReferences.get(id) || []; this.previousReferences.delete(id); const [modelId, agent] = await Promise.all([ - this.getModelId(id, request), - this.getAgent(id, request), + this.getModelId(id, request, token), + this.getAgent(id, request, token), ]); const session = await this.getOrCreateSession(request, chatSessionContext, modelId, agent, stream, disposables, token); if (!session || token.isCancellationRequested) { @@ -511,8 +510,8 @@ export class CopilotCLIChatSessionParticipant extends Disposable { * If opening an existing session, then uses the agent associated with that session. * If creating a new session with a prompt file that specifies an agent, then uses that agent. */ - private async getAgent(sessionId: string | undefined, request: vscode.ChatRequest | undefined): Promise { - const promptFile = request ? await this.getPromptInfoFromRequest(request) : undefined; + private async getAgent(sessionId: string | undefined, request: vscode.ChatRequest | undefined, token: vscode.CancellationToken): Promise { + const promptFile = request ? await this.getPromptInfoFromRequest(request, token) : undefined; if (promptFile?.header?.agent) { const agent = await this.copilotCLIAgents.resolveAgent(promptFile.header.agent); if (agent) { @@ -527,14 +526,13 @@ export class CopilotCLIChatSessionParticipant extends Disposable { return this.copilotCLIAgents.resolveAgent(sessionAgent ?? defaultAgent); } - private async getPromptInfoFromRequest(request: vscode.ChatRequest): Promise { + private async getPromptInfoFromRequest(request: vscode.ChatRequest, token: vscode.CancellationToken): Promise { const promptFile = new ChatVariablesCollection(request.references).find(isPromptFile); if (!promptFile || !URI.isUri(promptFile.reference.value)) { return undefined; } try { - const doc = await this.workspaceService.openTextDocument(promptFile.reference.value); - return new PromptFileParser().parse(promptFile.reference.value, doc.getText()); + return await this.promptsService.parseFile(promptFile.reference.value, token); } catch (ex) { this.logService.error(`Failed to parse the prompt file: ${promptFile.reference.value.toString()}`, ex); return undefined; @@ -571,8 +569,8 @@ export class CopilotCLIChatSessionParticipant extends Disposable { return session; } - private async getModelId(sessionId: string | undefined, request: vscode.ChatRequest | undefined): Promise { - const promptFile = request ? await this.getPromptInfoFromRequest(request) : undefined; + private async getModelId(sessionId: string | undefined, request: vscode.ChatRequest | undefined, token: vscode.CancellationToken): Promise { + const promptFile = request ? await this.getPromptInfoFromRequest(request, token) : undefined; if (promptFile?.header?.model) { const model = await this.copilotCLIModels.resolveModel(promptFile.header.model); if (model) { @@ -812,8 +810,8 @@ export class CopilotCLIChatSessionParticipant extends Disposable { } const [{ prompt, attachments }, model, agent] = await Promise.all([ this.promptResolver.resolvePrompt(request, requestPrompt, (references || []).concat([]), isolationEnabled, token), - this.getModelId(undefined, undefined), - this.getAgent(undefined, undefined), + this.getModelId(undefined, undefined, token), + this.getAgent(undefined, undefined, token), ]); const session = await this.sessionService.createSession({ workingDirectory, isolationEnabled, agent, model }, token); diff --git a/src/extension/chatSessions/vscode-node/test/copilotCLIChatSessionParticipant.spec.ts b/src/extension/chatSessions/vscode-node/test/copilotCLIChatSessionParticipant.spec.ts index 9b4a82418b..b85b330bc0 100644 --- a/src/extension/chatSessions/vscode-node/test/copilotCLIChatSessionParticipant.spec.ts +++ b/src/extension/chatSessions/vscode-node/test/copilotCLIChatSessionParticipant.spec.ts @@ -15,6 +15,7 @@ import { IVSCodeExtensionContext } from '../../../../platform/extContext/common/ import { MockFileSystemService } from '../../../../platform/filesystem/node/test/mockFileSystemService'; import { IGitService } from '../../../../platform/git/common/gitService'; import { ILogService } from '../../../../platform/log/common/logService'; +import { PromptsServiceImpl } from '../../../../platform/promptFiles/common/promptsServiceImpl'; import { NullTelemetryService } from '../../../../platform/telemetry/common/nullTelemetryService'; import type { ITelemetryService } from '../../../../platform/telemetry/common/telemetry'; import { IWorkspaceService, NullWorkspaceService } from '../../../../platform/workspace/common/workspaceService'; @@ -190,7 +191,7 @@ describe('CopilotCLIChatSessionParticipant.handleRequest', () => { configurationService, copilotSDK, logger, - new NullWorkspaceService() + new PromptsServiceImpl(new NullWorkspaceService()) ); }); diff --git a/src/extension/prompts/node/panel/promptFile.tsx b/src/extension/prompts/node/panel/promptFile.tsx index 96b35556b3..3d75628d81 100644 --- a/src/extension/prompts/node/panel/promptFile.tsx +++ b/src/extension/prompts/node/panel/promptFile.tsx @@ -5,7 +5,6 @@ import { BasePromptElementProps, PromptElement, PromptReference, PromptSizing } from '@vscode/prompt-tsx'; import type { ChatLanguageModelToolReference } from 'vscode'; -import { IFileSystemService } from '../../../../platform/filesystem/common/fileSystemService'; import { IIgnoreService } from '../../../../platform/ignore/common/ignoreService'; import { ILogService } from '../../../../platform/log/common/logService'; import { IPromptPathRepresentationService } from '../../../../platform/prompts/common/promptPathRepresentationService'; @@ -16,7 +15,6 @@ import { IPromptVariablesService } from '../../../prompt/node/promptVariablesSer import { EmbeddedInsideUserMessage } from '../base/promptElement'; import { Tag } from '../base/tag'; import { FilePathMode } from './fileVariable'; -import { isEqual } from '../../../../util/vs/base/common/resources'; export interface PromptFileProps extends BasePromptElementProps, EmbeddedInsideUserMessage { readonly variable: PromptVariable; @@ -28,7 +26,6 @@ export class PromptFile extends PromptElement { constructor( props: PromptFileProps, - @IFileSystemService private readonly fileSystemService: IFileSystemService, @IPromptVariablesService private readonly promptVariablesService: IPromptVariablesService, @ILogService private readonly logService: ILogService, @IPromptPathRepresentationService private readonly promptPathRepresentationService: IPromptPathRepresentationService, @@ -65,8 +62,8 @@ export class PromptFile extends PromptElement { private async getBodyContent(fileUri: URI, toolReferences: readonly ChatLanguageModelToolReference[] | undefined): Promise { try { - const documentText = this.workspaceService.textDocuments.find(doc => isEqual(doc.uri, fileUri))?.getText(); - let content = documentText ?? new TextDecoder().decode(await this.fileSystemService.readFile(fileUri)); + const doc = await this.workspaceService.openTextDocument(fileUri); + let content = doc.getText(); if (toolReferences && toolReferences.length > 0) { content = await this.promptVariablesService.resolveToolReferencesInPrompt(content, toolReferences); } diff --git a/src/platform/promptFiles/common/promptsService.ts b/src/platform/promptFiles/common/promptsService.ts index 697e5544d6..56de9588cc 100644 --- a/src/platform/promptFiles/common/promptsService.ts +++ b/src/platform/promptFiles/common/promptsService.ts @@ -22,6 +22,7 @@ export namespace PromptFileLangageId { * A service that provides prompt file related functionalities: agents, instructions and prompt files. */ export interface IPromptsService { + readonly _serviceBrand: undefined; /** * Reads and parses the provided URI * @param uris diff --git a/src/platform/promptFiles/common/promptsServiceImpl.ts b/src/platform/promptFiles/common/promptsServiceImpl.ts index fbdd75e08a..16d468e2c2 100644 --- a/src/platform/promptFiles/common/promptsServiceImpl.ts +++ b/src/platform/promptFiles/common/promptsServiceImpl.ts @@ -3,25 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { raceCancellationError } from '../../../util/vs/base/common/async'; import { CancellationToken } from '../../../util/vs/base/common/cancellation'; -import { CancellationError } from '../../../util/vs/base/common/errors'; import { URI } from '../../../util/vs/base/common/uri'; import { PromptFileParser } from '../../../util/vs/workbench/contrib/chat/common/promptSyntax/promptFileParser'; -import { IFileSystemService } from '../../filesystem/common/fileSystemService'; +import { IWorkspaceService } from '../../workspace/common/workspaceService'; import { IPromptsService, ParsedPromptFile } from './promptsService'; export class PromptsServiceImpl implements IPromptsService { - + declare _serviceBrand: undefined; constructor( - @IFileSystemService private readonly fileService: IFileSystemService + @IWorkspaceService private readonly workspaceService: IWorkspaceService ) { } public async parseFile(uri: URI, token: CancellationToken): Promise { - const fileContent = await this.fileService.readFile(uri); - if (token.isCancellationRequested) { - throw new CancellationError(); - } - const text = new TextDecoder().decode(fileContent); - return new PromptFileParser().parse(uri, text); + const doc = await raceCancellationError(this.workspaceService.openTextDocument(uri), token); + return new PromptFileParser().parse(uri, doc.getText()); } } \ No newline at end of file