Skip to content

Commit bf87124

Browse files
aeschliCopilot
andauthored
use IPromptsService to provide customizations (#309873)
* use IPromptsService to provide customizations * update * update * update * update * Update extensions/copilot/src/platform/promptFiles/test/common/mockPromptsService.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * update * dispose MockPromptsService * update --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 721ebb8 commit bf87124

26 files changed

+562
-828
lines changed

extensions/copilot/src/extension/chatSessions/common/chatCustomAgentsService.ts

Lines changed: 0 additions & 17 deletions
This file was deleted.

extensions/copilot/src/extension/chatSessions/common/chatPromptFileService.ts

Lines changed: 0 additions & 75 deletions
This file was deleted.

extensions/copilot/src/extension/chatSessions/copilotcli/node/copilotCLISkills.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ import {
2020
import { isObject } from '../../../../util/vs/base/common/types';
2121
import { URI } from '../../../../util/vs/base/common/uri';
2222
import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';
23-
import { IChatPromptFileService } from '../../common/chatPromptFileService';
23+
import { IPromptsService } from '../../../../platform/promptFiles/common/promptsService';
24+
import { CancellationToken } from '../../../../util/vs/base/common/cancellation';
2425

2526
export interface ICopilotCLISkills {
2627
readonly _serviceBrand: undefined;
27-
getSkillsLocations(): Uri[];
28+
getSkillsLocations(token: CancellationToken): Promise<Uri[]>;
2829
}
2930

3031
export const ICopilotCLISkills = createServiceIdentifier<ICopilotCLISkills>('ICopilotCLISkills');
@@ -37,12 +38,12 @@ export class CopilotCLISkills extends Disposable implements ICopilotCLISkills {
3738
@IConfigurationService private readonly configurationService: IConfigurationService,
3839
@INativeEnvService private readonly envService: INativeEnvService,
3940
@IWorkspaceService private readonly workspaceService: IWorkspaceService,
40-
@IChatPromptFileService private readonly chatPromptFileService: IChatPromptFileService,
41+
@IPromptsService private readonly promptsService: IPromptsService,
4142
) {
4243
super();
4344
}
4445

45-
public getSkillsLocations(): Uri[] {
46+
public async getSkillsLocations(token: CancellationToken): Promise<Uri[]> {
4647
// Get additional skill locations from config
4748
const configSkillLocationUris = new ResourceSet();
4849
const locations = this.configurationService.getNonExtensionConfig<Record<string, boolean>>(SKILLS_LOCATION_KEY);
@@ -68,7 +69,7 @@ export class CopilotCLISkills extends Disposable implements ICopilotCLISkills {
6869
}
6970
}
7071
}
71-
this.chatPromptFileService.skills
72+
(await this.promptsService.getSkills(token))
7273
.filter(s => s.uri.scheme === Schemas.file)
7374
.map(s => s.uri)
7475
.map(uri => dirname(dirname(uri)))

extensions/copilot/src/extension/chatSessions/copilotcli/node/copilotCli.ts

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { ConfigKey, IConfigurationService } from '../../../../platform/configura
1313
import { IEnvService } from '../../../../platform/env/common/envService';
1414
import { IVSCodeExtensionContext } from '../../../../platform/extContext/common/extensionContext';
1515
import { ILogService } from '../../../../platform/log/common/logService';
16-
import { type ParsedPromptFile } from '../../../../platform/promptFiles/common/promptsService';
16+
import { IPromptsService } from '../../../../platform/promptFiles/common/promptsService';
1717
import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService';
1818
import { createServiceIdentifier } from '../../../../util/common/services';
1919
import { Emitter, Event } from '../../../../util/vs/base/common/event';
@@ -22,10 +22,10 @@ import { Disposable } from '../../../../util/vs/base/common/lifecycle';
2222
import { basename } from '../../../../util/vs/base/common/resources';
2323
import { URI } from '../../../../util/vs/base/common/uri';
2424
import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';
25-
import { IChatPromptFileService } from '../../common/chatPromptFileService';
2625
import { getCopilotLogger } from './logger';
2726
import { ensureNodePtyShim } from './nodePtyShim';
2827
import { ensureRipgrepShim } from './ripgrepShim';
28+
import { CancellationToken } from '../../../../util/vs/base/common/cancellation';
2929

3030
export const COPILOT_CLI_REASONING_EFFORT_PROPERTY = 'reasoningEffort';
3131
const COPILOT_CLI_MODEL_MEMENTO_KEY = 'github.copilot.cli.sessionModel';
@@ -245,15 +245,15 @@ export class CopilotCLIAgents extends Disposable implements ICopilotCLIAgents {
245245
private readonly _onDidChangeAgents = this._register(new Emitter<void>());
246246
readonly onDidChangeAgents: Event<void> = this._onDidChangeAgents.event;
247247
constructor(
248-
@IChatPromptFileService private readonly chatPromptFileService: IChatPromptFileService,
248+
@IPromptsService private readonly promptsService: IPromptsService,
249249
@ICopilotCLISDK private readonly copilotCLISDK: ICopilotCLISDK,
250250
@IVSCodeExtensionContext private readonly extensionContext: IVSCodeExtensionContext,
251251
@ILogService private readonly logService: ILogService,
252252
@IWorkspaceService private readonly workspaceService: IWorkspaceService,
253253
) {
254254
super();
255255
void this.getAgents();
256-
this._register(this.chatPromptFileService.onDidChangeCustomAgents(() => {
256+
this._register(this.promptsService.onDidChangeCustomAgents(() => {
257257
this._refreshAgents();
258258
}));
259259
this._register(this.workspaceService.onDidChangeWorkspaceFolders(() => {
@@ -302,7 +302,7 @@ export class CopilotCLIAgents extends Disposable implements ICopilotCLIAgents {
302302
}
303303

304304
async resolveAgent(agentId: string): Promise<SweCustomAgent | undefined> {
305-
for (const promptFile of this.chatPromptFileService.customAgentPromptFiles) {
305+
for (const promptFile of await this.promptsService.getCustomAgents(CancellationToken.None)) {
306306
if (agentId === promptFile.uri.toString()) {
307307
return this.toCustomAgent(promptFile)?.agent;
308308
}
@@ -334,7 +334,7 @@ export class CopilotCLIAgents extends Disposable implements ICopilotCLIAgents {
334334
sourceUri: URI.from({ scheme: 'copilotcli', path: `/agents/${agent.name}` }),
335335
});
336336
}
337-
for (const promptFile of this.chatPromptFileService.customAgentPromptFiles) {
337+
for (const promptFile of await this.promptsService.getCustomAgents(CancellationToken.None)) {
338338
// Skip legacy .chatmode.md files — they are a deprecated format
339339
// and should not appear in the Copilot CLI agent list.
340340
if (promptFile.uri.path.toLowerCase().endsWith('.chatmode.md')) {
@@ -362,28 +362,31 @@ export class CopilotCLIAgents extends Disposable implements ICopilotCLIAgents {
362362
return agents.map(agent => this.cloneAgent(agent));
363363
}
364364

365-
private toCustomAgent(promptFile: ParsedPromptFile): CLIAgentInfo | undefined {
366-
const agentName = getAgentFileNameFromFilePath(promptFile.uri);
367-
const headerName = promptFile.header?.name?.trim();
365+
private toCustomAgent(customAgent: vscode.ChatCustomAgent): CLIAgentInfo | undefined {
366+
const agentName = getAgentFileNameFromFilePath(customAgent.uri);
367+
const headerName = customAgent.name;
368368
const name = headerName === undefined || headerName === '' ? agentName : headerName;
369369
if (!name) {
370370
return undefined;
371371
}
372372

373-
const tools = promptFile.header?.tools?.filter(tool => !!tool) ?? [];
374-
const model = promptFile.header?.model?.[0];
373+
const tools = customAgent.tools?.filter(tool => !!tool) ?? [];
374+
const model = customAgent.model?.[0];
375375

376376
return {
377377
agent: {
378378
name,
379379
displayName: name,
380-
description: promptFile.header?.description ?? '',
380+
description: customAgent.description ?? '',
381381
tools: tools.length > 0 ? tools : null,
382-
prompt: async () => promptFile.body?.getContent() ?? '',
383-
disableModelInvocation: promptFile.header?.disableModelInvocation ?? false,
382+
prompt: async () => {
383+
const pf = await this.promptsService.parseFile(customAgent.uri, CancellationToken.None);
384+
return pf.body?.getContent() ?? '';
385+
},
386+
disableModelInvocation: customAgent.disableModelInvocation ?? false,
384387
...(model ? { model } : {}),
385388
},
386-
sourceUri: promptFile.uri,
389+
sourceUri: customAgent.uri,
387390
};
388391
}
389392

extensions/copilot/src/extension/chatSessions/copilotcli/node/copilotcliPromptResolver.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { generateUserPrompt } from '../../../prompts/node/agent/copilotCLIPrompt
2323
import { getWorkingDirectory, isIsolationEnabled, IWorkspaceInfo } from '../../common/workspaceInfo';
2424
import { ICopilotCLIImageSupport, isImageMimeType } from './copilotCLIImageSupport';
2525
import { ICopilotCLISkills } from './copilotCLISkills';
26+
import { CancellationToken } from '../../../../util/vs/base/common/cancellation';
2627
import { IVSCodeExtensionContext } from '../../../../platform/extContext/common/extensionContext';
2728

2829
export class CopilotCLIPromptResolver {
@@ -77,7 +78,7 @@ export class CopilotCLIPromptResolver {
7778
const isolationEnabled = isIsolationEnabled(workspaceInfo) || additionalWorkspaces.some(ws => isIsolationEnabled(ws));
7879
const folderToWorktreeMap = this.buildFolderToWorktreeMap(workspaceInfo, additionalWorkspaces);
7980
const hasAnyWorkingDirectory = getWorkingDirectory(workspaceInfo) || additionalWorkspaces.some(ws => getWorkingDirectory(ws));
80-
const knownSkillLocations = this.skillsService.getSkillsLocations();
81+
const knownSkillLocations = await this.skillsService.getSkillsLocations(CancellationToken.None);
8182
await Promise.all(Array.from(variables).map(async variable => {
8283
// Unsupported references: prompt instructions, instruction files, and the customizations index.
8384
if (isInstructionFile(variable) || isCustomizationsIndex(variable)) {

extensions/copilot/src/extension/chatSessions/copilotcli/node/copilotcliSessionService.ts

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import * as l10n from '@vscode/l10n';
88
import { createReadStream } from 'node:fs';
99
import { devNull } from 'node:os';
1010
import { createInterface } from 'node:readline';
11-
import type { ChatRequest, ChatSessionItem } from 'vscode';
11+
import type { ChatCustomAgent, ChatRequest, ChatSessionItem } from 'vscode';
1212
import { IChatDebugFileLoggerService } from '../../../../platform/chat/common/chatDebugFileLoggerService';
1313
import { ConfigKey, IConfigurationService } from '../../../../platform/configuration/common/configurationService';
1414
import { INativeEnvService } from '../../../../platform/env/common/envService';
@@ -18,7 +18,7 @@ import { RelativePattern } from '../../../../platform/filesystem/common/fileType
1818
import { ILogService } from '../../../../platform/log/common/logService';
1919
import { deriveCopilotCliOTelEnv } from '../../../../platform/otel/common/agentOTelEnv';
2020
import { IOTelService } from '../../../../platform/otel/common/otelService';
21-
import { ParsedPromptFile } from '../../../../platform/promptFiles/common/promptsService';
21+
import { IPromptsService } from '../../../../platform/promptFiles/common/promptsService';
2222
import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService';
2323
import { createServiceIdentifier } from '../../../../util/common/services';
2424
import { coalesce } from '../../../../util/vs/base/common/arrays';
@@ -34,7 +34,6 @@ import { IInstantiationService } from '../../../../util/vs/platform/instantiatio
3434
import { ChatRequestTurn2, ChatResponseTurn2, ChatSessionStatus, Uri } from '../../../../vscodeTypes';
3535
import { IPromptVariablesService } from '../../../prompt/node/promptVariablesService';
3636
import { IAgentSessionsWorkspace } from '../../common/agentSessionsWorkspace';
37-
import { IChatPromptFileService } from '../../common/chatPromptFileService';
3837
import { IChatSessionMetadataStore, RequestDetails, StoredModeInstructions } from '../../common/chatSessionMetadataStore';
3938
import { IChatSessionWorkspaceFolderService } from '../../common/chatSessionWorkspaceFolderService';
4039
import { IChatSessionWorktreeService } from '../../common/chatSessionWorktreeService';
@@ -165,7 +164,7 @@ export class CopilotCLISessionService extends Disposable implements ICopilotCLIS
165164
@IOTelService private readonly _otelService: IOTelService,
166165
@IPromptVariablesService private readonly _promptVariablesService: IPromptVariablesService,
167166
@IChatDebugFileLoggerService private readonly _debugFileLogger: IChatDebugFileLoggerService,
168-
@IChatPromptFileService private readonly _chatPromptFileService: IChatPromptFileService,
167+
@IPromptsService private readonly _promptsService: IPromptsService,
169168
) {
170169
super();
171170
this.monitorSessionFiles();
@@ -647,7 +646,7 @@ export class CopilotCLISessionService extends Disposable implements ICopilotCLIS
647646
protected async createSessionsOptions(options: ICreateSessionOptions & { mcpServers?: SessionOptions['mcpServers'] }): Promise<Readonly<SessionOptions>> {
648647
const [agentInfos, skillLocations] = await Promise.all([
649648
this.agents.getAgents(),
650-
this.copilotCLISkills.getSkillsLocations(),
649+
this.copilotCLISkills.getSkillsLocations(CancellationToken.None),
651650
]);
652651
const customAgents = agentInfos.map(i => i.agent);
653652
const variablesContext = this._promptVariablesService.buildTemplateVariablesContext(options.sessionId, options.debugTargetSessionIds);
@@ -791,14 +790,14 @@ export class CopilotCLISessionService extends Disposable implements ICopilotCLIS
791790
const [agentId, storedDetails] = await Promise.all([agentIdPromise, requestDetailsPromise]);
792791

793792
// Build lookup from copilotRequestId → RequestDetails for the callback
794-
const customAgentLookup = this.createCustomAgentLookup();
793+
const customAgentLookup = await this.createCustomAgentLookup();
795794
const legacyMappings: RequestDetails[] = [];
796795
const detailsByCopilotId = new Map<string, RequestIdDetails>();
797-
const defaultModeInstructions = agentId ? this.resolveAgentModeInstructions(agentId, customAgentLookup) : undefined;
796+
const defaultModeInstructions = agentId ? await this.resolveAgentModeInstructions(agentId, customAgentLookup) : undefined;
798797

799798
for (const d of storedDetails) {
800799
if (d.copilotRequestId) {
801-
const modeInstructions = d.modeInstructions ?? this.resolveAgentModeInstructions(d.agentId, customAgentLookup) ?? defaultModeInstructions;
800+
const modeInstructions = d.modeInstructions ?? await this.resolveAgentModeInstructions(d.agentId, customAgentLookup) ?? defaultModeInstructions;
802801
detailsByCopilotId.set(d.copilotRequestId, { requestId: d.vscodeRequestId, toolIdEditMap: d.toolIdEditMap, modeInstructions });
803802
}
804803
}
@@ -831,36 +830,38 @@ export class CopilotCLISessionService extends Disposable implements ICopilotCLIS
831830
return { history, events };
832831
}
833832

834-
private createCustomAgentLookup(): Map<string, ParsedPromptFile> {
835-
const agents = this._chatPromptFileService.customAgentPromptFiles;
836-
const lookup = new Map<string, ParsedPromptFile>();
833+
private async createCustomAgentLookup(): Promise<Map<string, [ChatCustomAgent, Lazy<Promise<string>>]>> {
834+
const agents = await this._promptsService.getCustomAgents(CancellationToken.None);
835+
const lookup = new Map<string, [ChatCustomAgent, Lazy<Promise<string>>]>();
837836
for (const agent of agents) {
837+
const lazyContent = new Lazy(() => this._promptsService.parseFile(agent.uri, CancellationToken.None).then(parsed => parsed.body?.getContent() ?? ''));
838838
const keys = [
839-
agent.header?.name?.trim(),
839+
agent.name?.trim(),
840840
agent.uri.toString(),
841841
getAgentFileNameFromFilePath(agent.uri),
842842
];
843843
for (const key of keys) {
844844
if (key && !lookup.has(key)) {
845-
lookup.set(key, agent);
845+
lookup.set(key, [agent, lazyContent]);
846846
}
847847
}
848848
}
849849
return lookup;
850850
}
851851

852-
private resolveAgentModeInstructions(agentId: string | undefined, customAgentLookup: Map<string, ParsedPromptFile>): StoredModeInstructions | undefined {
852+
private async resolveAgentModeInstructions(agentId: string | undefined, customAgentLookup: Map<string, [ChatCustomAgent, Lazy<Promise<string>>]>): Promise<StoredModeInstructions | undefined> {
853853
if (!agentId) {
854854
return undefined;
855855
}
856-
const agent = customAgentLookup.get(agentId);
857-
if (!agent) {
856+
const agentEntry = customAgentLookup.get(agentId);
857+
if (!agentEntry) {
858858
return undefined;
859859
}
860+
const [agent, lazyContent] = agentEntry;
860861
return {
861862
uri: agent.uri.toString(),
862-
name: agent.header?.name?.trim() || agentId,
863-
content: agent.body?.getContent() ?? '',
863+
name: agent.name?.trim() || agentId,
864+
content: await lazyContent.value,
864865
};
865866
}
866867

0 commit comments

Comments
 (0)