Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ import {
import { isObject } from '../../../../util/vs/base/common/types';
import { URI } from '../../../../util/vs/base/common/uri';
import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';
import { IChatPromptFileService } from '../../common/chatPromptFileService';
import { IPromptsService } from '../../../../platform/promptFiles/common/promptsService';
import { CancellationToken } from '../../../../util/vs/base/common/cancellation';

export interface ICopilotCLISkills {
readonly _serviceBrand: undefined;
getSkillsLocations(): Uri[];
getSkillsLocations(token: CancellationToken): Promise<Uri[]>;
}

export const ICopilotCLISkills = createServiceIdentifier<ICopilotCLISkills>('ICopilotCLISkills');
Expand All @@ -37,12 +38,12 @@ export class CopilotCLISkills extends Disposable implements ICopilotCLISkills {
@IConfigurationService private readonly configurationService: IConfigurationService,
@INativeEnvService private readonly envService: INativeEnvService,
@IWorkspaceService private readonly workspaceService: IWorkspaceService,
@IChatPromptFileService private readonly chatPromptFileService: IChatPromptFileService,
@IPromptsService private readonly promptsService: IPromptsService,
) {
super();
}

public getSkillsLocations(): Uri[] {
public async getSkillsLocations(token: CancellationToken): Promise<Uri[]> {
// Get additional skill locations from config
const configSkillLocationUris = new ResourceSet();
const locations = this.configurationService.getNonExtensionConfig<Record<string, boolean>>(SKILLS_LOCATION_KEY);
Expand All @@ -68,7 +69,7 @@ export class CopilotCLISkills extends Disposable implements ICopilotCLISkills {
}
}
}
this.chatPromptFileService.skills
(await this.promptsService.getSkills(token))
.filter(s => s.uri.scheme === Schemas.file)
.map(s => s.uri)
.map(uri => dirname(dirname(uri)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { ConfigKey, IConfigurationService } from '../../../../platform/configura
import { IEnvService } from '../../../../platform/env/common/envService';
import { IVSCodeExtensionContext } from '../../../../platform/extContext/common/extensionContext';
import { ILogService } from '../../../../platform/log/common/logService';
import { type ParsedPromptFile } from '../../../../platform/promptFiles/common/promptsService';
import { IPromptsService } from '../../../../platform/promptFiles/common/promptsService';
import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService';
import { createServiceIdentifier } from '../../../../util/common/services';
import { Emitter, Event } from '../../../../util/vs/base/common/event';
Expand All @@ -22,10 +22,10 @@ import { Disposable } from '../../../../util/vs/base/common/lifecycle';
import { basename } from '../../../../util/vs/base/common/resources';
import { URI } from '../../../../util/vs/base/common/uri';
import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';
import { IChatPromptFileService } from '../../common/chatPromptFileService';
import { getCopilotLogger } from './logger';
import { ensureNodePtyShim } from './nodePtyShim';
import { ensureRipgrepShim } from './ripgrepShim';
import { CancellationToken } from '../../../../util/vs/base/common/cancellation';

export const COPILOT_CLI_REASONING_EFFORT_PROPERTY = 'reasoningEffort';
const COPILOT_CLI_MODEL_MEMENTO_KEY = 'github.copilot.cli.sessionModel';
Expand Down Expand Up @@ -245,15 +245,15 @@ export class CopilotCLIAgents extends Disposable implements ICopilotCLIAgents {
private readonly _onDidChangeAgents = this._register(new Emitter<void>());
readonly onDidChangeAgents: Event<void> = this._onDidChangeAgents.event;
constructor(
@IChatPromptFileService private readonly chatPromptFileService: IChatPromptFileService,
@IPromptsService private readonly promptsService: IPromptsService,
@ICopilotCLISDK private readonly copilotCLISDK: ICopilotCLISDK,
@IVSCodeExtensionContext private readonly extensionContext: IVSCodeExtensionContext,
@ILogService private readonly logService: ILogService,
@IWorkspaceService private readonly workspaceService: IWorkspaceService,
) {
super();
void this.getAgents();
this._register(this.chatPromptFileService.onDidChangeCustomAgents(() => {
this._register(this.promptsService.onDidChangeCustomAgents(() => {
this._refreshAgents();
}));
this._register(this.workspaceService.onDidChangeWorkspaceFolders(() => {
Expand Down Expand Up @@ -302,7 +302,7 @@ export class CopilotCLIAgents extends Disposable implements ICopilotCLIAgents {
}

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

private toCustomAgent(promptFile: ParsedPromptFile): CLIAgentInfo | undefined {
const agentName = getAgentFileNameFromFilePath(promptFile.uri);
const headerName = promptFile.header?.name?.trim();
private toCustomAgent(customAgent: vscode.ChatCustomAgent): CLIAgentInfo | undefined {
const agentName = getAgentFileNameFromFilePath(customAgent.uri);
const headerName = customAgent.name;
const name = headerName === undefined || headerName === '' ? agentName : headerName;
if (!name) {
return undefined;
}

const tools = promptFile.header?.tools?.filter(tool => !!tool) ?? [];
const model = promptFile.header?.model?.[0];
const tools = customAgent.tools?.filter(tool => !!tool) ?? [];
const model = customAgent.model?.[0];

return {
agent: {
name,
displayName: name,
description: promptFile.header?.description ?? '',
description: customAgent.description ?? '',
tools: tools.length > 0 ? tools : null,
prompt: async () => promptFile.body?.getContent() ?? '',
disableModelInvocation: promptFile.header?.disableModelInvocation ?? false,
prompt: async () => {
const pf = await this.promptsService.parseFile(customAgent.uri, CancellationToken.None);
return pf.body?.getContent() ?? '';
},
disableModelInvocation: customAgent.disableModelInvocation ?? false,
...(model ? { model } : {}),
},
sourceUri: promptFile.uri,
sourceUri: customAgent.uri,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { generateUserPrompt } from '../../../prompts/node/agent/copilotCLIPrompt
import { getWorkingDirectory, isIsolationEnabled, IWorkspaceInfo } from '../../common/workspaceInfo';
import { ICopilotCLIImageSupport, isImageMimeType } from './copilotCLIImageSupport';
import { ICopilotCLISkills } from './copilotCLISkills';
import { CancellationToken } from '../../../../util/vs/base/common/cancellation';
import { IVSCodeExtensionContext } from '../../../../platform/extContext/common/extensionContext';

export class CopilotCLIPromptResolver {
Expand Down Expand Up @@ -77,7 +78,7 @@ export class CopilotCLIPromptResolver {
const isolationEnabled = isIsolationEnabled(workspaceInfo) || additionalWorkspaces.some(ws => isIsolationEnabled(ws));
const folderToWorktreeMap = this.buildFolderToWorktreeMap(workspaceInfo, additionalWorkspaces);
const hasAnyWorkingDirectory = getWorkingDirectory(workspaceInfo) || additionalWorkspaces.some(ws => getWorkingDirectory(ws));
const knownSkillLocations = this.skillsService.getSkillsLocations();
const knownSkillLocations = await this.skillsService.getSkillsLocations(CancellationToken.None);
await Promise.all(Array.from(variables).map(async variable => {
// Unsupported references: prompt instructions, instruction files, and the customizations index.
if (isInstructionFile(variable) || isCustomizationsIndex(variable)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { createReadStream } from 'node:fs';
import * as fs from 'node:fs/promises';
import { devNull, EOL } from 'node:os';
import { createInterface } from 'node:readline';
import type { ChatRequest, ChatSessionItem } from 'vscode';
import type { ChatCustomAgent, ChatRequest, ChatSessionItem } from 'vscode';
import { IChatDebugFileLoggerService } from '../../../../platform/chat/common/chatDebugFileLoggerService';
import { ConfigKey, IConfigurationService } from '../../../../platform/configuration/common/configurationService';
import { INativeEnvService } from '../../../../platform/env/common/envService';
Expand All @@ -19,7 +19,7 @@ import { RelativePattern } from '../../../../platform/filesystem/common/fileType
import { ILogService } from '../../../../platform/log/common/logService';
import { deriveCopilotCliOTelEnv } from '../../../../platform/otel/common/agentOTelEnv';
import { IOTelService } from '../../../../platform/otel/common/otelService';
import { ParsedPromptFile } from '../../../../platform/promptFiles/common/promptsService';
import { IPromptsService } from '../../../../platform/promptFiles/common/promptsService';
import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService';
import { createServiceIdentifier } from '../../../../util/common/services';
import { coalesce } from '../../../../util/vs/base/common/arrays';
Expand All @@ -36,7 +36,6 @@ import { IInstantiationService } from '../../../../util/vs/platform/instantiatio
import { ChatRequestTurn2, ChatResponseTurn2, ChatSessionStatus, Uri } from '../../../../vscodeTypes';
import { IPromptVariablesService } from '../../../prompt/node/promptVariablesService';
import { IAgentSessionsWorkspace } from '../../common/agentSessionsWorkspace';
import { IChatPromptFileService } from '../../common/chatPromptFileService';
import { IChatSessionMetadataStore, RequestDetails, StoredModeInstructions } from '../../common/chatSessionMetadataStore';
import { IChatSessionWorkspaceFolderService } from '../../common/chatSessionWorkspaceFolderService';
import { IChatSessionWorktreeService } from '../../common/chatSessionWorktreeService';
Expand Down Expand Up @@ -171,7 +170,7 @@ export class CopilotCLISessionService extends Disposable implements ICopilotCLIS
@IOTelService private readonly _otelService: IOTelService,
@IPromptVariablesService private readonly _promptVariablesService: IPromptVariablesService,
@IChatDebugFileLoggerService private readonly _debugFileLogger: IChatDebugFileLoggerService,
@IChatPromptFileService private readonly _chatPromptFileService: IChatPromptFileService,
@IPromptsService private readonly _promptsService: IPromptsService,
) {
super();
this.monitorSessionFiles();
Expand Down Expand Up @@ -654,7 +653,7 @@ export class CopilotCLISessionService extends Disposable implements ICopilotCLIS
protected async createSessionsOptions(options: ICreateSessionOptions & { mcpServers?: SessionOptions['mcpServers'] }): Promise<Readonly<SessionOptions>> {
const [agentInfos, skillLocations] = await Promise.all([
this.agents.getAgents(),
this.copilotCLISkills.getSkillsLocations(),
this.copilotCLISkills.getSkillsLocations(CancellationToken.None),
]);
const customAgents = agentInfos.map(i => i.agent);
const variablesContext = this._promptVariablesService.buildTemplateVariablesContext(options.sessionId, options.debugTargetSessionIds);
Expand Down Expand Up @@ -798,14 +797,14 @@ export class CopilotCLISessionService extends Disposable implements ICopilotCLIS
const [agentId, storedDetails] = await Promise.all([agentIdPromise, requestDetailsPromise]);

// Build lookup from copilotRequestId → RequestDetails for the callback
const customAgentLookup = this.createCustomAgentLookup();
const customAgentLookup = await this.createCustomAgentLookup();
const legacyMappings: RequestDetails[] = [];
const detailsByCopilotId = new Map<string, RequestIdDetails>();
const defaultModeInstructions = agentId ? this.resolveAgentModeInstructions(agentId, customAgentLookup) : undefined;
const defaultModeInstructions = agentId ? await this.resolveAgentModeInstructions(agentId, customAgentLookup) : undefined;

for (const d of storedDetails) {
if (d.copilotRequestId) {
const modeInstructions = d.modeInstructions ?? this.resolveAgentModeInstructions(d.agentId, customAgentLookup) ?? defaultModeInstructions;
const modeInstructions = d.modeInstructions ?? await this.resolveAgentModeInstructions(d.agentId, customAgentLookup) ?? defaultModeInstructions;
detailsByCopilotId.set(d.copilotRequestId, { requestId: d.vscodeRequestId, toolIdEditMap: d.toolIdEditMap, modeInstructions });
}
}
Expand Down Expand Up @@ -838,36 +837,38 @@ export class CopilotCLISessionService extends Disposable implements ICopilotCLIS
return { history, events };
}

private createCustomAgentLookup(): Map<string, ParsedPromptFile> {
const agents = this._chatPromptFileService.customAgentPromptFiles;
const lookup = new Map<string, ParsedPromptFile>();
private async createCustomAgentLookup(): Promise<Map<string, [ChatCustomAgent, Lazy<Promise<string>>]>> {
const agents = await this._promptsService.getCustomAgents(CancellationToken.None);
const lookup = new Map<string, [ChatCustomAgent, Lazy<Promise<string>>]>();
for (const agent of agents) {
const lazyContent = new Lazy(() => this._promptsService.parseFile(agent.uri, CancellationToken.None).then(parsed => parsed.body?.getContent() ?? ''));
const keys = [
agent.header?.name?.trim(),
agent.name?.trim(),
agent.uri.toString(),
getAgentFileNameFromFilePath(agent.uri),
];
for (const key of keys) {
if (key && !lookup.has(key)) {
lookup.set(key, agent);
lookup.set(key, [agent, lazyContent]);
}
}
}
return lookup;
}

private resolveAgentModeInstructions(agentId: string | undefined, customAgentLookup: Map<string, ParsedPromptFile>): StoredModeInstructions | undefined {
private async resolveAgentModeInstructions(agentId: string | undefined, customAgentLookup: Map<string, [ChatCustomAgent, Lazy<Promise<string>>]>): Promise<StoredModeInstructions | undefined> {
if (!agentId) {
return undefined;
}
const agent = customAgentLookup.get(agentId);
if (!agent) {
const agentEntry = customAgentLookup.get(agentId);
if (!agentEntry) {
return undefined;
}
const [agent, lazyContent] = agentEntry;
return {
uri: agent.uri.toString(),
name: agent.header?.name?.trim() || agentId,
content: agent.body?.getContent() ?? '',
name: agent.name?.trim() || agentId,
content: await lazyContent.value,
};
}

Expand Down
Loading
Loading