From b156f50b6e6cf4c2063f5d3860f4123415a0a34e Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 1 Apr 2026 15:22:28 +0200 Subject: [PATCH 1/6] Move chat debug logging out of PromptsService --- .../chat/browser/promptsDebugContribution.ts | 178 +++++++++++++----- .../contrib/chat/common/chatDebugService.ts | 1 + .../chat/common/chatService/chatService.ts | 2 +- .../common/chatService/chatServiceImpl.ts | 4 +- .../promptSyntax/service/promptsService.ts | 31 +-- .../service/promptsServiceImpl.ts | 126 ++++--------- .../browser/promptsDebugContribution.test.ts | 98 ++++++---- .../service/mockPromptsService.ts | 6 +- .../service/promptsService.test.ts | 15 +- 9 files changed, 233 insertions(+), 228 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/promptsDebugContribution.ts b/src/vs/workbench/contrib/chat/browser/promptsDebugContribution.ts index 79dab9d272962..1e00ad159ff4e 100644 --- a/src/vs/workbench/contrib/chat/browser/promptsDebugContribution.ts +++ b/src/vs/workbench/contrib/chat/browser/promptsDebugContribution.ts @@ -3,18 +3,18 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; import { generateUuid } from '../../../../base/common/uuid.js'; +import { localize } from '../../../../nls.js'; import { IWorkbenchContribution } from '../../../common/contributions.js'; import { IChatDebugResolvedEventContent, IChatDebugService } from '../common/chatDebugService.js'; -import { IPromptDiscoveryInfo, IPromptsService } from '../common/promptSyntax/service/promptsService.js'; +import { IChatService } from '../common/chatService/chatService.js'; +import { PromptsType } from '../common/promptSyntax/promptTypes.js'; +import { IHookDiscoveryInfo, IPromptDiscoveryInfo, IPromptsService } from '../common/promptSyntax/service/promptsService.js'; /** - * Bridges {@link IPromptsService} discovery log events to {@link IChatDebugService}. - * - * This contribution listens for discovery events emitted by the prompts service - * and forwards them as debug log entries. It also registers a resolve provider - * so expanding a discovery event in the Agent Debug Logs shows the full file list. + * Bridges prompt discovery information to {@link IChatDebugService}. */ export class PromptsDebugContribution extends Disposable implements IWorkbenchContribution { @@ -29,59 +29,71 @@ export class PromptsDebugContribution extends Disposable implements IWorkbenchCo private readonly _discoveryEventDetails = new Map(); constructor( - @IPromptsService promptsService: IPromptsService, + @IPromptsService private readonly promptsService: IPromptsService, + @IChatService chatService: IChatService, @IChatDebugService chatDebugService: IChatDebugService, ) { super(); // Forward discovery log events to the debug service. - this._register(promptsService.onDidLogDiscovery(entry => { - let eventId: string | undefined; - - if (entry.discoveryInfo) { - eventId = generateUuid(); - this._discoveryEventDetails.set(eventId, entry.discoveryInfo); - - // Evict oldest entries when the map exceeds the cap. - if (this._discoveryEventDetails.size > PromptsDebugContribution.MAX_DISCOVERY_DETAILS) { - const first = this._discoveryEventDetails.keys().next().value; - if (first !== undefined) { - this._discoveryEventDetails.delete(first); + this._register(chatService.onDidSubmitRequest(async chatRequest => { + const sessionResource = chatRequest.chatSessionResource; + const cts = new CancellationTokenSource(); + + try { + for (const promptType of [PromptsType.agent, PromptsType.instructions, PromptsType.prompt, PromptsType.skill, PromptsType.hook]) { + + const { discoveryInfo, name, details, category } = await this.getDiscoveryLogEntry(promptType, cts.token); + const eventId = generateUuid(); + + this._discoveryEventDetails.set(eventId, discoveryInfo); + + // Evict oldest entries when the map exceeds the cap. + if (this._discoveryEventDetails.size > PromptsDebugContribution.MAX_DISCOVERY_DETAILS) { + const first = this._discoveryEventDetails.keys().next().value; + if (first !== undefined) { + this._discoveryEventDetails.delete(first); + } } - } - } - // Enrich details with file paths so they appear in the event - // payload (e.g. forwarded via onDidReceiveChatDebugEvent to the - // extension's JSONL file logger). - let details = entry.details; - if (entry.discoveryInfo) { - const info = entry.discoveryInfo; - const loaded = info.files - .filter(f => f.status === 'loaded') - .map(f => f.promptPath.name ?? f.promptPath.uri.path.split('/').pop() ?? f.promptPath.uri.toString()); - const skipped = info.files.filter(f => f.status === 'skipped').map(f => { - const label = f.promptPath.uri.toString(); - return f.skipReason ? `${label} (${f.skipReason})` : label; - }); - const folders = info.sourceFolders?.map(sf => sf.uri.path) ?? []; - const parts: string[] = []; - if (details) { parts.push(details); } - if (loaded.length > 0) { parts.push(`loaded: [${truncateList(loaded)}]`); } - if (skipped.length > 0) { parts.push(`skipped: [${truncateList(skipped)}]`); } - if (folders.length > 0) { parts.push(`folders: [${truncateList(folders)}]`); } - details = parts.join(' | ') || undefined; + // Enrich details with file paths so they appear in the event + // payload (e.g. forwarded via onDidReceiveChatDebugEvent to the + // extension's JSONL file logger). + const loaded = discoveryInfo.files + .filter(f => f.status === 'loaded') + .map(f => f.promptPath.name ?? f.promptPath.uri.path.split('/').pop() ?? f.promptPath.uri.toString()); + const skipped = discoveryInfo.files.filter(f => f.status === 'skipped').map(f => { + const label = f.promptPath.uri.toString(); + return f.skipReason ? `${label} (${f.skipReason})` : label; + }); + const folders = discoveryInfo.sourceFolders?.map(sf => sf.uri.path) ?? []; + const parts: string[] = []; + if (details) { + parts.push(details); + } + if (loaded.length > 0) { + parts.push(`loaded: [${truncateList(loaded)}]`); + } + if (skipped.length > 0) { + parts.push(`skipped: [${truncateList(skipped)}]`); + } + if (folders.length > 0) { + parts.push(`folders: [${truncateList(folders)}]`); + } + const newDetails = parts.join(' | ') || undefined; + + chatDebugService.log( + sessionResource, + name, + newDetails, + undefined, + { id: eventId, category }, + ); + } + } finally { + cts.dispose(); } - - chatDebugService.log( - entry.sessionResource, - entry.name, - details, - undefined, - { id: eventId, category: entry.category }, - ); })); - // Register a resolve provider so expanding a discovery event // in the Agent Debug Logs shows the full file list. this._register(chatDebugService.registerProvider({ @@ -92,6 +104,70 @@ export class PromptsDebugContribution extends Disposable implements IWorkbenchCo })); } + private async getDiscoveryLogEntry(promptType: PromptsType, token: CancellationToken): Promise<{ readonly name: string; readonly details?: string; readonly category: 'discovery'; readonly discoveryInfo: IPromptDiscoveryInfo }> { + const discoveryInfo = await this.promptsService.getDiscoveryInfo(promptType, token); + const durationInMillis = discoveryInfo.durationInMillis; + const loadedCount = discoveryInfo.files.filter(file => file.status === 'loaded').length; + const skippedCount = discoveryInfo.files.length - loadedCount; + + switch (promptType) { + case PromptsType.prompt: + return { + name: localize('promptsService.loadSlashCommands', 'Load Slash Commands'), + details: loadedCount === 1 + ? localize('promptsDebugContribution.resolvedSlashCommand', 'Resolved {0} slash command in {1}ms', loadedCount, durationInMillis) + : localize('promptsDebugContribution.resolvedSlashCommands', 'Resolved {0} slash commands in {1}ms', loadedCount, durationInMillis), + category: 'discovery', + discoveryInfo + }; + case PromptsType.agent: + return { + name: localize('promptsService.loadAgents', 'Load Agents'), + details: loadedCount === 1 + ? localize('promptsDebugContribution.resolvedAgent', 'Resolved {0} agent in {1}ms', loadedCount, durationInMillis) + : localize('promptsDebugContribution.resolvedAgents', 'Resolved {0} agents in {1}ms', loadedCount, durationInMillis), + category: 'discovery', + discoveryInfo + + }; + case PromptsType.skill: + return { + name: localize('promptsService.loadSkills', 'Load Skills'), + details: loadedCount === 1 + ? localize('promptsDebugContribution.resolvedSkill', 'Resolved {0} skill in {1}ms', loadedCount, durationInMillis) + : localize('promptsDebugContribution.resolvedSkills', 'Resolved {0} skills in {1}ms', loadedCount, durationInMillis), + category: 'discovery', + discoveryInfo + }; + case PromptsType.instructions: + return { + name: localize('promptsService.loadInstructions', 'Load Instructions'), + details: loadedCount === 1 + ? localize('promptsDebugContribution.resolvedInstruction', 'Resolved {0} instruction in {1}ms', loadedCount, durationInMillis) + : localize('promptsDebugContribution.resolvedInstructions', 'Resolved {0} instructions in {1}ms', loadedCount, durationInMillis), + category: 'discovery', + discoveryInfo + }; + case PromptsType.hook: { + const hookDiscoveryInfo = discoveryInfo as IHookDiscoveryInfo; + const hookCount = hookDiscoveryInfo.hooksInfo + ? Object.values(hookDiscoveryInfo.hooksInfo.hooks).reduce((total, hooks) => total + hooks.length, 0) + : loadedCount; + const details = skippedCount > 0 + ? localize('promptsDebugContribution.resolvedHooksWithSkipped', 'Resolved {0} hooks from {1} files in {2}ms, skipped {3}', hookCount, loadedCount, durationInMillis, skippedCount) + : hookCount === 1 + ? localize('promptsDebugContribution.resolvedHook', 'Resolved {0} hook in {1}ms', hookCount, durationInMillis) + : localize('promptsDebugContribution.resolvedHooks', 'Resolved {0} hooks in {1}ms', hookCount, durationInMillis); + return { + name: localize('promptsService.loadHooks', 'Load Hooks'), + details, + category: 'discovery', + discoveryInfo + }; + } + } + } + private _resolveDiscoveryEvent(eventId: string): IChatDebugResolvedEventContent | undefined { const info = this._discoveryEventDetails.get(eventId); if (!info) { @@ -101,6 +177,7 @@ export class PromptsDebugContribution extends Disposable implements IWorkbenchCo return { kind: 'fileList', discoveryType: info.type, + durationInMillis: info.durationInMillis, files: info.files.map(f => ({ uri: f.promptPath.uri, name: f.promptPath.name, @@ -129,5 +206,6 @@ function truncateList(items: string[]): string { if (items.length <= MAX_LIST_ITEMS) { return items.join(', '); } + return items.slice(0, MAX_LIST_ITEMS).join(', ') + ` (+${items.length - MAX_LIST_ITEMS} more)`; } diff --git a/src/vs/workbench/contrib/chat/common/chatDebugService.ts b/src/vs/workbench/contrib/chat/common/chatDebugService.ts index e54c6abc16812..5c0e4daf9565a 100644 --- a/src/vs/workbench/contrib/chat/common/chatDebugService.ts +++ b/src/vs/workbench/contrib/chat/common/chatDebugService.ts @@ -294,6 +294,7 @@ export interface IChatDebugSourceFolderEntry { export interface IChatDebugEventFileListContent { readonly kind: 'fileList'; readonly discoveryType: string; + readonly durationInMillis: number; readonly files: readonly IChatDebugFileEntry[]; readonly sourceFolders?: readonly IChatDebugSourceFolderEntry[]; } diff --git a/src/vs/workbench/contrib/chat/common/chatService/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService/chatService.ts index 79201b41d44bb..5000b8fc79300 100644 --- a/src/vs/workbench/contrib/chat/common/chatService/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService/chatService.ts @@ -1407,7 +1407,7 @@ export interface IChatService { _serviceBrand: undefined; transferredSessionResource: URI | undefined; - readonly onDidSubmitRequest: Event<{ readonly chatSessionResource: URI; readonly message?: IParsedChatRequest }>; + readonly onDidSubmitRequest: Event<{ readonly chatSessionResource: URI; readonly message?: IParsedChatRequest; readonly options?: Readonly }>; readonly onDidCreateModel: Event; diff --git a/src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts index 99a2a4b4f9709..bb38567ac0b10 100644 --- a/src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts @@ -114,7 +114,7 @@ export class ChatService extends Disposable implements IChatService { return this._transferredSessionResource; } - private readonly _onDidSubmitRequest = this._register(new Emitter<{ readonly chatSessionResource: URI; readonly message?: IParsedChatRequest }>()); + private readonly _onDidSubmitRequest = this._register(new Emitter<{ readonly chatSessionResource: URI; readonly message?: IParsedChatRequest; readonly options?: Readonly }>()); public readonly onDidSubmitRequest = this._onDidSubmitRequest.event; public get onDidCreateModel() { return this._sessionModels.onDidCreateModel; } @@ -1427,7 +1427,7 @@ export class ChatService extends Disposable implements IChatService { if (options?.userSelectedModelId) { this.languageModelsService.addToRecentlyUsedList(options.userSelectedModelId); } - this._onDidSubmitRequest.fire({ chatSessionResource: model.sessionResource, message: parsedRequest }); + this._onDidSubmitRequest.fire({ chatSessionResource: model.sessionResource, message: parsedRequest, options: options }); return { responseCreatedPromise: responseCreated.p, responseCompletePromise: rawResponsePromise, diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts index 748205a9a1a2e..efe5182b6848c 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts @@ -18,19 +18,6 @@ import { ResourceSet } from '../../../../../../base/common/map.js'; import { IResolvedPromptSourceFolder } from '../config/promptFileLocations.js'; import { ChatRequestHooks } from '../hookSchema.js'; -/** - * Entry emitted by the prompts service when discovery logging occurs. - * A debug bridge (e.g. contribution) can listen and forward these to IChatDebugService. - */ -export interface IPromptDiscoveryLogEntry { - readonly sessionResource: URI; - readonly name: string; - readonly details?: string; - readonly category?: string; - /** When present, the bridge should store this for later event resolution. */ - readonly discoveryInfo?: IPromptDiscoveryInfo; -} - /** * Activation events for prompt file providers. */ @@ -366,6 +353,8 @@ export interface IPromptSourceFolderResult { export interface IPromptDiscoveryInfo { readonly type: PromptsType; readonly files: readonly IPromptFileDiscoveryResult[]; + /** Time in milliseconds required to compute this discovery result. */ + readonly durationInMillis: number; /** Source folders that were searched */ readonly sourceFolders?: readonly IPromptSourceFolderResult[]; } @@ -398,16 +387,6 @@ export interface IAgentDiscoveryInfo extends IPromptDiscoveryInfo { readonly files: readonly IAgentDiscoveryResult[]; } -export function sanitizePromptDiscoveryInfo(info: IPromptDiscoveryInfo): IPromptDiscoveryInfo { - return { - ...info, - files: info.files.map(file => ({ - ...file, - errorMessage: file.errorMessage ? 'REDACTED' : undefined, - })), - }; -} - export interface IConfiguredHooksInfo { readonly hooks: ChatRequestHooks; readonly hasDisabledClaudeHooks: boolean; @@ -576,8 +555,8 @@ export interface IPromptsService extends IDisposable { getInstructionFiles(token: CancellationToken, sessionResource?: URI): Promise; /** - * Fired when a discovery-related log entry is produced. - * Listeners (such as a debug bridge) can forward these to IChatDebugService. + * Returns the cached discovery info for the given prompt type. */ - readonly onDidLogDiscovery: Event; + getDiscoveryInfo(type: PromptsType, token: CancellationToken): Promise; + } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index dbb6ce266a02b..90e2cf05a9d44 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -8,6 +8,7 @@ import { CancellationError } from '../../../../../../base/common/errors.js'; import { Emitter, Event } from '../../../../../../base/common/event.js'; import { parse as parseJSONC } from '../../../../../../base/common/json.js'; import { Disposable, DisposableStore, IDisposable } from '../../../../../../base/common/lifecycle.js'; +import { StopWatch } from '../../../../../../base/common/stopwatch.js'; import { autorun, IReader } from '../../../../../../base/common/observable.js'; import { ResourceMap, ResourceSet } from '../../../../../../base/common/map.js'; import { basename, dirname, isEqual } from '../../../../../../base/common/resources.js'; @@ -36,7 +37,7 @@ import { PromptFilesLocator } from '../utils/promptFilesLocator.js'; import { PromptFileParser, ParsedPromptFile, PromptHeaderAttributes } from '../promptFileParser.js'; -import { IAgentInstructions, type IAgentSource, IChatPromptSlashCommand, IConfiguredHooksInfo, ICustomAgent, IExtensionPromptPath, ILocalPromptPath, IPluginPromptPath, IPromptPath, IPromptsService, IAgentSkill, IUserPromptPath, PromptsStorage, CUSTOM_AGENT_PROVIDER_ACTIVATION_EVENT, INSTRUCTIONS_PROVIDER_ACTIVATION_EVENT, IPromptFileContext, IPromptFileResource, PROMPT_FILE_PROVIDER_ACTIVATION_EVENT, SKILL_PROVIDER_ACTIVATION_EVENT, IPromptDiscoveryInfo, IPromptFileDiscoveryResult, IPromptSourceFolderResult, ICustomAgentVisibility, IResolvedAgentFile, AgentFileType, Logger, IPromptDiscoveryLogEntry, ISlashCommandDiscoveryInfo, ISlashCommandDiscoveryResult, IAgentDiscoveryInfo, IAgentDiscoveryResult, IHookDiscoveryInfo } from './promptsService.js'; +import { IAgentInstructions, type IAgentSource, IChatPromptSlashCommand, IConfiguredHooksInfo, ICustomAgent, IExtensionPromptPath, ILocalPromptPath, IPluginPromptPath, IPromptPath, IPromptsService, IAgentSkill, IUserPromptPath, PromptsStorage, CUSTOM_AGENT_PROVIDER_ACTIVATION_EVENT, INSTRUCTIONS_PROVIDER_ACTIVATION_EVENT, IPromptFileContext, IPromptFileResource, PROMPT_FILE_PROVIDER_ACTIVATION_EVENT, SKILL_PROVIDER_ACTIVATION_EVENT, IPromptDiscoveryInfo, IPromptFileDiscoveryResult, IPromptSourceFolderResult, ICustomAgentVisibility, IResolvedAgentFile, AgentFileType, Logger, ISlashCommandDiscoveryInfo, ISlashCommandDiscoveryResult, IAgentDiscoveryInfo, IAgentDiscoveryResult, IHookDiscoveryInfo } from './promptsService.js'; import { Delayer } from '../../../../../../base/common/async.js'; import { Schemas } from '../../../../../../base/common/network.js'; import { ChatRequestHooks, parseSubagentHooksFromYaml } from '../hookSchema.js'; @@ -47,7 +48,6 @@ import { IWorkspaceContextService } from '../../../../../../platform/workspace/c import { IWorkspaceTrustManagementService } from '../../../../../../platform/workspace/common/workspaceTrust.js'; import { IPathService } from '../../../../../services/path/common/pathService.js'; import { getTarget, mapClaudeModels, mapClaudeTools } from '../languageProviders/promptFileAttributes.js'; -import { StopWatch } from '../../../../../../base/common/stopwatch.js'; import { ContextKeyExpr, IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js'; import { getCanonicalPluginCommandId, IAgentPlugin, IAgentPluginService } from '../../plugins/agentPluginService.js'; import { isContributionEnabled } from '../../enablement.js'; @@ -126,13 +126,6 @@ export class PromptsService extends Disposable implements IPromptsService { */ private readonly cachedParsedPromptFromModels = new ResourceMap<[number, ParsedPromptFile]>(); - /** - * Emitter for discovery log events. Listeners (e.g. a debug bridge - * contribution) can forward these to IChatDebugService. - */ - private readonly _onDidLogDiscovery = this._register(new Emitter()); - public readonly onDidLogDiscovery: Event = this._onDidLogDiscovery.event; - /** * Cached file locations commands. Caching only happens if the corresponding `fileLocatorEvents` event is used. */ @@ -597,23 +590,9 @@ export class PromptsService extends Disposable implements IPromptsService { return this.cachedSlashCommands.onDidChangePromise; } - public async getPromptSlashCommands(token: CancellationToken, sessionResource?: URI): Promise { - const sw = StopWatch.create(); + public async getPromptSlashCommands(token: CancellationToken, _sessionResource?: URI): Promise { const discoveryInfo = await this.cachedSlashCommands.get(token); const result = this.slashCommandsFromDiscoveryInfo(discoveryInfo); - if (sessionResource) { - const elapsed = sw.elapsed(); - const details = result.length === 1 - ? localize("promptsService.resolvedSlashCommand", "Resolved {0} slash command in {1}ms", result.length, elapsed.toFixed(1)) - : localize("promptsService.resolvedSlashCommands", "Resolved {0} slash commands in {1}ms", result.length, elapsed.toFixed(1)); - this._onDidLogDiscovery.fire({ - sessionResource, - name: localize("promptsService.loadSlashCommands", "Load Slash Commands"), - details, - discoveryInfo, - category: 'discovery', - }); - } return result; } @@ -621,6 +600,7 @@ export class PromptsService extends Disposable implements IPromptsService { * Computes discovery info for slash commands, combining prompts and skills. */ private async computeSlashCommandDiscoveryInfo(token: CancellationToken): Promise { + const stopWatch = StopWatch.create(true); const promptFiles = await this.listPromptFiles(PromptsType.prompt, token); const useAgentSkills = this.configurationService.getValue(PromptsConfig.USE_AGENT_SKILLS); const skills = useAgentSkills ? await this.listPromptFiles(PromptsType.skill, token) : []; @@ -651,7 +631,7 @@ export class PromptsService extends Disposable implements IPromptsService { const skillSourceFolders = await this._collectSourceFolderDiagnostics(PromptsType.skill); sourceFolders.push(...skillSourceFolders); } - return { type: PromptsType.prompt, files, sourceFolders }; + return { type: PromptsType.prompt, files, sourceFolders, durationInMillis: stopWatch.elapsed() }; } /** @@ -723,23 +703,9 @@ export class PromptsService extends Disposable implements IPromptsService { return this.cachedInstructions.onDidChangePromise; } - public async getCustomAgents(token: CancellationToken, sessionResource?: URI): Promise { - const sw = StopWatch.create(); + public async getCustomAgents(token: CancellationToken, _sessionResource?: URI): Promise { const discoveryInfo = await this.cachedCustomAgents.get(token); const result = this.agentsFromDiscoveryInfo(discoveryInfo); - if (sessionResource) { - const elapsed = sw.elapsed(); - const details = result.length === 1 - ? localize("promptsService.resolvedAgent", "Resolved {0} agent in {1}ms", result.length, elapsed.toFixed(1)) - : localize("promptsService.resolvedAgents", "Resolved {0} agents in {1}ms", result.length, elapsed.toFixed(1)); - this._onDidLogDiscovery.fire({ - sessionResource, - name: localize("promptsService.loadAgents", "Load Agents"), - details, - discoveryInfo, - category: 'discovery', - }); - } return result; } @@ -757,6 +723,7 @@ export class PromptsService extends Disposable implements IPromptsService { } private async computeAgentDiscoveryInfo(token: CancellationToken): Promise { + const stopWatch = StopWatch.create(true); const allAgentFiles = await this.listPromptFiles(PromptsType.agent, token); const disabledAgents = this.getDisabledPromptFiles(PromptsType.agent); @@ -857,7 +824,7 @@ export class PromptsService extends Disposable implements IPromptsService { })); const sourceFolders = await this._collectSourceFolderDiagnostics(PromptsType.agent); - return { type: PromptsType.agent, files, sourceFolders }; + return { type: PromptsType.agent, files, sourceFolders, durationInMillis: stopWatch.elapsed() }; } @@ -1145,28 +1112,14 @@ export class PromptsService extends Disposable implements IPromptsService { return this.cachedSkills.onDidChangePromise; } - public async findAgentSkills(token: CancellationToken, sessionResource?: URI): Promise { + public async findAgentSkills(token: CancellationToken, _sessionResource?: URI): Promise { const useAgentSkills = this.configurationService.getValue(PromptsConfig.USE_AGENT_SKILLS); if (!useAgentSkills) { return undefined; } - const sw = StopWatch.create(); const discoveryInfo = await this.cachedSkills.get(token); const result = this.skillsFromDiscoveryInfo(discoveryInfo); - if (sessionResource) { - const elapsed = sw.elapsed(); - const details = result.length === 1 - ? localize("promptsService.resolvedSkill", "Resolved {0} skill in {1}ms", result.length, elapsed.toFixed(1)) - : localize("promptsService.resolvedSkills", "Resolved {0} skills in {1}ms", result.length, elapsed.toFixed(1)); - this._onDidLogDiscovery.fire({ - sessionResource, - name: localize("promptsService.loadSkills", "Load Skills"), - details, - discoveryInfo, - category: 'discovery', - }); - } return result; } @@ -1198,6 +1151,7 @@ export class PromptsService extends Disposable implements IPromptsService { * Computes the full skill discovery info, including source folders and telemetry. */ private async computeSkillDiscovery(token: CancellationToken): Promise { + const stopWatch = StopWatch.create(true); const files = await this.computeSkillDiscoveryInfo(token); const sourceFolders = await this._collectSourceFolderDiagnostics(PromptsType.skill); @@ -1294,47 +1248,33 @@ export class PromptsService extends Disposable implements IPromptsService { skippedParseFailed }); - return { type: PromptsType.skill, files, sourceFolders }; + return { type: PromptsType.skill, files, sourceFolders, durationInMillis: stopWatch.elapsed() }; } - public async getHooks(token: CancellationToken, sessionResource?: URI): Promise { - const sw = StopWatch.create(); + public async getHooks(token: CancellationToken, _sessionResource?: URI): Promise { const discoveryInfo = await this.cachedHooks.get(token); const result = discoveryInfo.hooksInfo; - if (sessionResource) { - const elapsed = sw.elapsed(); - const hookCount = result ? Object.values(result.hooks).reduce((sum, arr) => sum + arr.length, 0) : 0; - const details = hookCount === 1 - ? localize("promptsService.resolvedHook", "Resolved {0} hook in {1}ms", hookCount, elapsed.toFixed(1)) - : localize("promptsService.resolvedHooks", "Resolved {0} hooks in {1}ms", hookCount, elapsed.toFixed(1)); - this._onDidLogDiscovery.fire({ - sessionResource, - name: localize("promptsService.loadHooks", "Load Hooks"), - details, - discoveryInfo, - category: 'discovery', - }); - } return result; } - public async getInstructionFiles(token: CancellationToken, sessionResource?: URI): Promise { - const sw = StopWatch.create(); + public async getDiscoveryInfo(type: PromptsType, token: CancellationToken): Promise { + switch (type) { + case PromptsType.instructions: + return this.cachedInstructions.get(token); + case PromptsType.prompt: + return this.cachedSlashCommands.get(token); + case PromptsType.agent: + return this.cachedCustomAgents.get(token); + case PromptsType.skill: + return this.cachedSkills.get(token); + case PromptsType.hook: + return this.cachedHooks.get(token); + } + } + + public async getInstructionFiles(token: CancellationToken, _sessionResource?: URI): Promise { const discoveryInfo = await this.cachedInstructions.get(token); const result = discoveryInfo.files.filter(file => file.status === 'loaded').map(file => file.promptPath); - if (sessionResource) { - const elapsed = sw.elapsed(); - const details = result.length === 1 - ? localize("promptsService.resolvedInstruction", "Resolved {0} instruction in {1}ms", result.length, elapsed.toFixed(1)) - : localize("promptsService.resolvedInstructions", "Resolved {0} instructions in {1}ms", result.length, elapsed.toFixed(1)); - this._onDidLogDiscovery.fire({ - sessionResource, - name: localize("promptsService.loadInstructions", "Load Instructions"), - details, - discoveryInfo, - category: 'discovery', - }); - } return result; } @@ -1347,6 +1287,7 @@ export class PromptsService extends Disposable implements IPromptsService { } private async computeHooks(token: CancellationToken): Promise { + const stopWatch = StopWatch.create(true); const useChatHooks = this.configurationService.getValue(PromptsConfig.USE_CHAT_HOOKS); if (!useChatHooks || !this.workspaceTrustService.isWorkspaceTrusted()) { @@ -1358,7 +1299,7 @@ export class PromptsService extends Disposable implements IPromptsService { promptPath: this.withPromptPathMetadata(promptPath, basename(promptPath.uri), promptPath.description), })); const sourceFolders = await this._collectSourceFolderDiagnostics(PromptsType.hook); - return { type: PromptsType.hook, files, sourceFolders, hooksInfo: undefined }; + return { type: PromptsType.hook, files, sourceFolders, hooksInfo: undefined, durationInMillis: stopWatch.elapsed() }; } const useClaudeHooks = this.configurationService.getValue(PromptsConfig.USE_CLAUDE_HOOKS); @@ -1516,14 +1457,14 @@ export class PromptsService extends Disposable implements IPromptsService { // Check if any hooks were collected if (collectedHooks.size === 0) { this.logger.trace('[PromptsService] No valid hooks collected.'); - return { type: PromptsType.hook, files, sourceFolders, hooksInfo: undefined }; + return { type: PromptsType.hook, files, sourceFolders, hooksInfo: undefined, durationInMillis: stopWatch.elapsed() }; } // Build the result const result: ChatRequestHooks = Object.fromEntries(collectedHooks) as ChatRequestHooks; this.logger.trace(`[PromptsService] Collected hooks: ${JSON.stringify(Object.keys(result))}`); - return { type: PromptsType.hook, files, sourceFolders, hooksInfo: { hooks: result, hasDisabledClaudeHooks } }; + return { type: PromptsType.hook, files, sourceFolders, hooksInfo: { hooks: result, hasDisabledClaudeHooks }, durationInMillis: stopWatch.elapsed() }; } /** @@ -1611,6 +1552,7 @@ export class PromptsService extends Disposable implements IPromptsService { } private async getInstructionsDiscoveryInfo(token: CancellationToken): Promise { + const stopWatch = StopWatch.create(true); const files: IPromptFileDiscoveryResult[] = []; const instructionsFiles = await this.listPromptFiles(PromptsType.instructions, token); @@ -1636,7 +1578,7 @@ export class PromptsService extends Disposable implements IPromptsService { } const sourceFolders = await this._collectSourceFolderDiagnostics(PromptsType.instructions); - return { type: PromptsType.instructions, files, sourceFolders }; + return { type: PromptsType.instructions, files, sourceFolders, durationInMillis: stopWatch.elapsed() }; } } diff --git a/src/vs/workbench/contrib/chat/test/browser/promptsDebugContribution.test.ts b/src/vs/workbench/contrib/chat/test/browser/promptsDebugContribution.test.ts index dc3a322b1353b..18cd019d3ce02 100644 --- a/src/vs/workbench/contrib/chat/test/browser/promptsDebugContribution.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/promptsDebugContribution.test.ts @@ -9,10 +9,11 @@ import { URI } from '../../../../../base/common/uri.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; import { ChatDebugLogLevel, IChatDebugEvent, IChatDebugGenericEvent, IChatDebugService } from '../../common/chatDebugService.js'; +import { IChatService } from '../../common/chatService/chatService.js'; import { ChatDebugServiceImpl } from '../../common/chatDebugServiceImpl.js'; import { LocalChatSessionUri } from '../../common/model/chatUri.js'; import { PromptsDebugContribution } from '../../browser/promptsDebugContribution.js'; -import { ILocalPromptPath, IPromptDiscoveryLogEntry, IPromptDiscoveryInfo, IPromptsService, PromptsStorage } from '../../common/promptSyntax/service/promptsService.js'; +import { ILocalPromptPath, IPromptDiscoveryInfo, IPromptsService, PromptsStorage } from '../../common/promptSyntax/service/promptsService.js'; import { PromptsType } from '../../common/promptSyntax/promptTypes.js'; function createLocalPromptPath(path: string, name: string): ILocalPromptPath { @@ -24,12 +25,22 @@ function createLocalPromptPath(path: string, name: string): ILocalPromptPath { }; } +function isGenericEvent(event: IChatDebugEvent): event is IChatDebugGenericEvent { + return event.kind === 'generic'; +} + +async function flushAsyncLogging(): Promise { + await new Promise(resolve => setTimeout(resolve, 0)); +} + suite('PromptsDebugContribution', () => { const disposables = ensureNoDisposablesAreLeakedInTestSuite(); let chatDebugService: ChatDebugServiceImpl; - let promptsOnDidLogDiscovery: Emitter; + let submitRequestEmitter: Emitter<{ readonly chatSessionResource: URI }>; let instaService: TestInstantiationService; + let promptsService: Partial; + const emptyDiscoveryInfo = (type: PromptsType): IPromptDiscoveryInfo => ({ type, files: [], durationInMillis: 0 }); setup(() => { instaService = disposables.add(new TestInstantiationService()); @@ -37,29 +48,39 @@ suite('PromptsDebugContribution', () => { chatDebugService = disposables.add(new ChatDebugServiceImpl()); instaService.stub(IChatDebugService, chatDebugService); - promptsOnDidLogDiscovery = disposables.add(new Emitter()); - instaService.stub(IPromptsService, { onDidLogDiscovery: promptsOnDidLogDiscovery.event } as Partial); + submitRequestEmitter = disposables.add(new Emitter<{ readonly chatSessionResource: URI }>()); + instaService.stub(IChatService, { onDidSubmitRequest: submitRequestEmitter.event } as Partial); + promptsService = { + getDiscoveryInfo: async type => emptyDiscoveryInfo(type), + }; + instaService.stub(IPromptsService, promptsService); }); - test('should forward discovery events to chat debug service', () => { + test('should forward discovery events to chat debug service', async () => { disposables.add(instaService.createInstance(PromptsDebugContribution)); const firedEvents: IChatDebugEvent[] = []; disposables.add(chatDebugService.onDidAddEvent(e => firedEvents.push(e))); - promptsOnDidLogDiscovery.fire({ - sessionResource: LocalChatSessionUri.forSession('session-1'), - name: 'Load Instructions', - details: 'Resolved 3 instructions in 12.5ms', - category: 'discovery', + promptsService.getDiscoveryInfo = async type => ({ + type, + durationInMillis: 7, + files: type === PromptsType.instructions ? [{ + status: 'loaded' as const, + promptPath: createLocalPromptPath('/workspace/.github/instructions/test.instructions.md', 'test.instructions.md'), + }] : [], }); - assert.strictEqual(firedEvents.length, 1); - const event = firedEvents[0] as IChatDebugGenericEvent; + submitRequestEmitter.fire({ chatSessionResource: LocalChatSessionUri.forSession('session-1') }); + await flushAsyncLogging(); + + assert.strictEqual(firedEvents.length, 5); + const event = firedEvents.find((e): e is IChatDebugGenericEvent => isGenericEvent(e) && e.name === 'Load Instructions'); + assert.ok(event); assert.strictEqual(event.kind, 'generic'); assert.ok(event.sessionResource); assert.strictEqual(event.name, 'Load Instructions'); - assert.strictEqual(event.details, 'Resolved 3 instructions in 12.5ms'); + assert.ok(event.details?.includes('Resolved 1 instruction')); assert.strictEqual(event.category, 'discovery'); }); @@ -71,6 +92,7 @@ suite('PromptsDebugContribution', () => { const discoveryInfo: IPromptDiscoveryInfo = { type: PromptsType.instructions, + durationInMillis: 11, files: [{ status: 'loaded' as const, promptPath: createLocalPromptPath('/workspace/.github/instructions/test.instructions.md', 'test.instructions.md'), @@ -81,16 +103,13 @@ suite('PromptsDebugContribution', () => { }], }; - promptsOnDidLogDiscovery.fire({ - sessionResource: LocalChatSessionUri.forSession('session-1'), - name: 'Discovery End', - details: '1 loaded, 0 skipped', - category: 'discovery', - discoveryInfo, - }); + promptsService.getDiscoveryInfo = async type => type === PromptsType.instructions ? discoveryInfo : emptyDiscoveryInfo(type); + submitRequestEmitter.fire({ chatSessionResource: LocalChatSessionUri.forSession('session-1') }); + await flushAsyncLogging(); - assert.strictEqual(firedEvents.length, 1); - const eventId = firedEvents[0].id; + const instructionsEvent = firedEvents.find((e): e is IChatDebugGenericEvent => isGenericEvent(e) && e.name === 'Load Instructions'); + assert.ok(instructionsEvent); + const eventId = instructionsEvent.id; assert.ok(eventId, 'Event should have an ID for resolution'); const resolved = await chatDebugService.resolveEvent(eventId); @@ -98,6 +117,7 @@ suite('PromptsDebugContribution', () => { assert.strictEqual(resolved.kind, 'fileList'); if (resolved.kind === 'fileList') { assert.strictEqual(resolved.discoveryType, 'instructions'); + assert.strictEqual(resolved.durationInMillis, 11); assert.strictEqual(resolved.files.length, 1); assert.strictEqual(resolved.files[0].name, 'test.instructions.md'); assert.strictEqual(resolved.files[0].status, 'loaded'); @@ -112,20 +132,18 @@ suite('PromptsDebugContribution', () => { assert.strictEqual(resolved, undefined); }); - test('should not assign event id when no discoveryInfo', () => { + test('should assign event id when discoveryInfo is empty', async () => { disposables.add(instaService.createInstance(PromptsDebugContribution)); const firedEvents: IChatDebugEvent[] = []; disposables.add(chatDebugService.onDidAddEvent(e => firedEvents.push(e))); - promptsOnDidLogDiscovery.fire({ - sessionResource: LocalChatSessionUri.forSession('session-1'), - name: 'Discovery Start', - category: 'discovery', - }); + promptsService.getDiscoveryInfo = async type => emptyDiscoveryInfo(type); + submitRequestEmitter.fire({ chatSessionResource: LocalChatSessionUri.forSession('session-1') }); + await flushAsyncLogging(); - assert.strictEqual(firedEvents.length, 1); - assert.strictEqual(firedEvents[0].id, undefined, 'Event without discoveryInfo should have no id'); + assert.strictEqual(firedEvents.length, 5); + assert.ok(firedEvents.every(e => e.id !== undefined), 'Events with discovery info should have an id'); }); test('should handle discoveryInfo with skipped files', async () => { @@ -136,6 +154,7 @@ suite('PromptsDebugContribution', () => { const discoveryInfo: IPromptDiscoveryInfo = { type: PromptsType.instructions, + durationInMillis: 5, files: [ { status: 'loaded' as const, @@ -149,13 +168,11 @@ suite('PromptsDebugContribution', () => { ], }; - promptsOnDidLogDiscovery.fire({ - sessionResource: LocalChatSessionUri.forSession('session-1'), - name: 'Discovery End', - discoveryInfo, - }); + promptsService.getDiscoveryInfo = async type => type === PromptsType.instructions ? discoveryInfo : emptyDiscoveryInfo(type); + submitRequestEmitter.fire({ chatSessionResource: LocalChatSessionUri.forSession('session-1') }); + await flushAsyncLogging(); - const eventId = firedEvents[0].id!; + const eventId = firedEvents.find((e): e is IChatDebugGenericEvent => isGenericEvent(e) && e.name === 'Load Instructions')!.id!; const resolved = await chatDebugService.resolveEvent(eventId); assert.ok(resolved); if (resolved.kind === 'fileList') { @@ -166,16 +183,15 @@ suite('PromptsDebugContribution', () => { } }); - test('should handle level as undefined (defaults to Info)', () => { + test('should handle level as undefined (defaults to Info)', async () => { disposables.add(instaService.createInstance(PromptsDebugContribution)); const firedEvents: IChatDebugEvent[] = []; disposables.add(chatDebugService.onDidAddEvent(e => firedEvents.push(e))); - promptsOnDidLogDiscovery.fire({ - sessionResource: LocalChatSessionUri.forSession('session-1'), - name: 'Test', - }); + promptsService.getDiscoveryInfo = async type => emptyDiscoveryInfo(type); + submitRequestEmitter.fire({ chatSessionResource: LocalChatSessionUri.forSession('session-1') }); + await flushAsyncLogging(); const event = firedEvents[0] as IChatDebugGenericEvent; assert.strictEqual(event.level, ChatDebugLogLevel.Info, 'Default level should be Info'); diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/mockPromptsService.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/mockPromptsService.ts index d9ef7bdde738b..441a7dad9fbcf 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/mockPromptsService.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/mockPromptsService.ts @@ -11,7 +11,7 @@ import { ITextModel } from '../../../../../../../editor/common/model.js'; import { IExtensionDescription } from '../../../../../../../platform/extensions/common/extensions.js'; import { PromptsType } from '../../../../common/promptSyntax/promptTypes.js'; import { ParsedPromptFile } from '../../../../common/promptSyntax/promptFileParser.js'; -import { IAgentSkill, ICustomAgent, IPromptFileContext, IPromptFileResource, IPromptPath, IPromptsService, IResolvedAgentFile, PromptsStorage, IPromptDiscoveryLogEntry } from '../../../../common/promptSyntax/service/promptsService.js'; +import { IAgentSkill, ICustomAgent, IPromptDiscoveryInfo, IPromptFileContext, IPromptFileResource, IPromptPath, IPromptsService, IResolvedAgentFile, PromptsStorage } from '../../../../common/promptSyntax/service/promptsService.js'; import { ResourceSet } from '../../../../../../../base/common/map.js'; export class MockPromptsService implements IPromptsService { @@ -21,9 +21,6 @@ export class MockPromptsService implements IPromptsService { private readonly _onDidChangeCustomAgents = new Emitter(); readonly onDidChangeCustomAgents = this._onDidChangeCustomAgents.event; - private readonly _onDidLogDiscovery = new Emitter(); - readonly onDidLogDiscovery: Event = this._onDidLogDiscovery.event; - private _customModes: ICustomAgent[] = []; setCustomModes(modes: ICustomAgent[]): void { @@ -69,6 +66,7 @@ export class MockPromptsService implements IPromptsService { // eslint-disable-next-line @typescript-eslint/no-explicit-any getHooks(_token: CancellationToken, _sessionResource?: URI): Promise { throw new Error('Method not implemented.'); } getInstructionFiles(_token: CancellationToken, _sessionResource?: URI): Promise { throw new Error('Method not implemented.'); } + getDiscoveryInfo(_type: PromptsType, _token: CancellationToken): Promise { throw new Error('Method not implemented.'); } dispose(): void { } onDidChangeInstructions: Event = Event.None; onDidChangePromptFiles: Event = Event.None; diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts index 242fe405992a6..2fa737d14d3e0 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts @@ -41,7 +41,7 @@ import { ComputeAutomaticInstructions, newInstructionsCollectionEvent } from '.. import { PromptsConfig } from '../../../../common/promptSyntax/config/config.js'; import { AGENTS_SOURCE_FOLDER, CLAUDE_CONFIG_FOLDER, HOOKS_SOURCE_FOLDER, INSTRUCTION_FILE_EXTENSION, INSTRUCTIONS_DEFAULT_SOURCE_FOLDER, LEGACY_MODE_DEFAULT_SOURCE_FOLDER, PROMPT_DEFAULT_SOURCE_FOLDER, PROMPT_FILE_EXTENSION } from '../../../../common/promptSyntax/config/promptFileLocations.js'; import { INSTRUCTIONS_LANGUAGE_ID, PROMPT_LANGUAGE_ID, PromptFileSource, PromptsType, Target } from '../../../../common/promptSyntax/promptTypes.js'; -import { ICustomAgent, IPromptDiscoveryInfo, IPromptDiscoveryLogEntry, IPromptFileContext, IPromptsService, PromptsStorage } from '../../../../common/promptSyntax/service/promptsService.js'; +import { ICustomAgent, IPromptFileContext, IPromptsService, PromptsStorage } from '../../../../common/promptSyntax/service/promptsService.js'; import { PromptsService } from '../../../../common/promptSyntax/service/promptsServiceImpl.js'; import { mockFiles } from '../testUtils/mockFilesystem.js'; import { InMemoryStorageService, IStorageService } from '../../../../../../../platform/storage/common/storage.js'; @@ -3936,17 +3936,8 @@ suite('PromptsService', () => { testPluginsObservable.set([plugin], undefined); - // Use a sessionResource to trigger discovery logging - const sessionResource = URI.file('/session'); - let capturedDiscoveryInfo: IPromptDiscoveryInfo | undefined; - const logDisposable = service.onDidLogDiscovery((entry: IPromptDiscoveryLogEntry) => { - if (entry.discoveryInfo?.type === PromptsType.hook) { - capturedDiscoveryInfo = entry.discoveryInfo; - } - }); - - const result = await service.getHooks(CancellationToken.None, sessionResource); - logDisposable.dispose(); + const result = await service.getHooks(CancellationToken.None, URI.file('/session')); + const capturedDiscoveryInfo = await service.getDiscoveryInfo(PromptsType.hook, CancellationToken.None); assert.ok(result, 'Expected hooks result with plugin hooks'); assert.ok(capturedDiscoveryInfo, 'Expected discovery info to be logged'); From ffdb286762321b270d8d45da29e6003b0a27ca7f Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 1 Apr 2026 16:13:21 +0200 Subject: [PATCH 2/6] remove sessionResource arg from PromptsService getters --- .../contrib/chat/browser/promptsService.ts | 4 +- .../chat/browser/promptsDebugContribution.ts | 37 +++---- .../contrib/chat/browser/widget/chatWidget.ts | 3 +- .../common/chatService/chatServiceImpl.ts | 4 +- .../computeAutomaticInstructions.ts | 7 +- .../promptSyntax/service/promptsService.ts | 15 +-- .../service/promptsServiceImpl.ts | 10 +- .../tools/builtinTools/runSubagentTool.ts | 4 +- .../common/chatService/chatService.test.ts | 4 +- .../computeAutomaticInstructions.test.ts | 96 ++++++++----------- .../service/mockPromptsService.ts | 10 +- .../service/promptsService.test.ts | 2 +- 12 files changed, 85 insertions(+), 111 deletions(-) diff --git a/src/vs/sessions/contrib/chat/browser/promptsService.ts b/src/vs/sessions/contrib/chat/browser/promptsService.ts index 13e2ef24d7695..63d6748ed4e86 100644 --- a/src/vs/sessions/contrib/chat/browser/promptsService.ts +++ b/src/vs/sessions/contrib/chat/browser/promptsService.ts @@ -122,8 +122,8 @@ export class AgenticPromptsService extends PromptsService { * Override to include built-in skills, appending them with lowest priority. * Skills from any other source (workspace, user, extension, internal) take precedence. */ - public override async findAgentSkills(token: CancellationToken, sessionResource?: URI): Promise { - const baseResult = await super.findAgentSkills(token, sessionResource); + public override async findAgentSkills(token: CancellationToken): Promise { + const baseResult = await super.findAgentSkills(token); if (baseResult === undefined) { return undefined; } diff --git a/src/vs/workbench/contrib/chat/browser/promptsDebugContribution.ts b/src/vs/workbench/contrib/chat/browser/promptsDebugContribution.ts index 1e00ad159ff4e..b44ee4aa4d411 100644 --- a/src/vs/workbench/contrib/chat/browser/promptsDebugContribution.ts +++ b/src/vs/workbench/contrib/chat/browser/promptsDebugContribution.ts @@ -3,10 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js'; +import { CancellationTokenSource } from '../../../../base/common/cancellation.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; import { generateUuid } from '../../../../base/common/uuid.js'; import { localize } from '../../../../nls.js'; +import { ILogService } from '../../../../platform/log/common/log.js'; import { IWorkbenchContribution } from '../../../common/contributions.js'; import { IChatDebugResolvedEventContent, IChatDebugService } from '../common/chatDebugService.js'; import { IChatService } from '../common/chatService/chatService.js'; @@ -32,6 +33,7 @@ export class PromptsDebugContribution extends Disposable implements IWorkbenchCo @IPromptsService private readonly promptsService: IPromptsService, @IChatService chatService: IChatService, @IChatDebugService chatDebugService: IChatDebugService, + @ILogService logService: ILogService, ) { super(); @@ -42,8 +44,8 @@ export class PromptsDebugContribution extends Disposable implements IWorkbenchCo try { for (const promptType of [PromptsType.agent, PromptsType.instructions, PromptsType.prompt, PromptsType.skill, PromptsType.hook]) { - - const { discoveryInfo, name, details, category } = await this.getDiscoveryLogEntry(promptType, cts.token); + const discoveryInfo = await this.promptsService.getDiscoveryInfo(promptType, cts.token); + const { name, details } = await this.getDiscoveryLogEntry(promptType, discoveryInfo); const eventId = generateUuid(); this._discoveryEventDetails.set(eventId, discoveryInfo); @@ -87,9 +89,11 @@ export class PromptsDebugContribution extends Disposable implements IWorkbenchCo name, newDetails, undefined, - { id: eventId, category }, + { id: eventId, category: 'discovery' }, ); } + } catch (error) { + logService.error('Error while logging prompt discovery info to chat debug service', error); } finally { cts.dispose(); } @@ -104,8 +108,8 @@ export class PromptsDebugContribution extends Disposable implements IWorkbenchCo })); } - private async getDiscoveryLogEntry(promptType: PromptsType, token: CancellationToken): Promise<{ readonly name: string; readonly details?: string; readonly category: 'discovery'; readonly discoveryInfo: IPromptDiscoveryInfo }> { - const discoveryInfo = await this.promptsService.getDiscoveryInfo(promptType, token); + private async getDiscoveryLogEntry(promptType: PromptsType, discoveryInfo: IPromptDiscoveryInfo): Promise<{ readonly name: string; readonly details?: string }> { + const durationInMillis = discoveryInfo.durationInMillis; const loadedCount = discoveryInfo.files.filter(file => file.status === 'loaded').length; const skippedCount = discoveryInfo.files.length - loadedCount; @@ -116,37 +120,28 @@ export class PromptsDebugContribution extends Disposable implements IWorkbenchCo name: localize('promptsService.loadSlashCommands', 'Load Slash Commands'), details: loadedCount === 1 ? localize('promptsDebugContribution.resolvedSlashCommand', 'Resolved {0} slash command in {1}ms', loadedCount, durationInMillis) - : localize('promptsDebugContribution.resolvedSlashCommands', 'Resolved {0} slash commands in {1}ms', loadedCount, durationInMillis), - category: 'discovery', - discoveryInfo + : localize('promptsDebugContribution.resolvedSlashCommands', 'Resolved {0} slash commands in {1}ms', loadedCount, durationInMillis) }; case PromptsType.agent: return { name: localize('promptsService.loadAgents', 'Load Agents'), details: loadedCount === 1 ? localize('promptsDebugContribution.resolvedAgent', 'Resolved {0} agent in {1}ms', loadedCount, durationInMillis) - : localize('promptsDebugContribution.resolvedAgents', 'Resolved {0} agents in {1}ms', loadedCount, durationInMillis), - category: 'discovery', - discoveryInfo - + : localize('promptsDebugContribution.resolvedAgents', 'Resolved {0} agents in {1}ms', loadedCount, durationInMillis) }; case PromptsType.skill: return { name: localize('promptsService.loadSkills', 'Load Skills'), details: loadedCount === 1 ? localize('promptsDebugContribution.resolvedSkill', 'Resolved {0} skill in {1}ms', loadedCount, durationInMillis) - : localize('promptsDebugContribution.resolvedSkills', 'Resolved {0} skills in {1}ms', loadedCount, durationInMillis), - category: 'discovery', - discoveryInfo + : localize('promptsDebugContribution.resolvedSkills', 'Resolved {0} skills in {1}ms', loadedCount, durationInMillis) }; case PromptsType.instructions: return { name: localize('promptsService.loadInstructions', 'Load Instructions'), details: loadedCount === 1 ? localize('promptsDebugContribution.resolvedInstruction', 'Resolved {0} instruction in {1}ms', loadedCount, durationInMillis) - : localize('promptsDebugContribution.resolvedInstructions', 'Resolved {0} instructions in {1}ms', loadedCount, durationInMillis), - category: 'discovery', - discoveryInfo + : localize('promptsDebugContribution.resolvedInstructions', 'Resolved {0} instructions in {1}ms', loadedCount, durationInMillis) }; case PromptsType.hook: { const hookDiscoveryInfo = discoveryInfo as IHookDiscoveryInfo; @@ -160,9 +155,7 @@ export class PromptsDebugContribution extends Disposable implements IWorkbenchCo : localize('promptsDebugContribution.resolvedHooks', 'Resolved {0} hooks in {1}ms', hookCount, durationInMillis); return { name: localize('promptsService.loadHooks', 'Load Hooks'), - details, - category: 'discovery', - discoveryInfo + details }; } } diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts index 7689707940f77..b5c9b92672834 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts @@ -2817,8 +2817,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this.logService.debug(`ChatWidget#_autoAttachInstructions: prompt files are enabled`); const enabledTools = this.input.currentModeKind === ChatModeKind.Agent ? this.input.selectedToolsModel.userSelectedTools.get() : undefined; const enabledSubAgents = this.input.currentModeKind === ChatModeKind.Agent ? this.input.currentModeObs.get().agents?.get() : undefined; - const sessionResource = this._viewModel?.model.sessionResource; - const computer = this.instantiationService.createInstance(ComputeAutomaticInstructions, this.input.currentModeKind, enabledTools, enabledSubAgents, sessionResource); + const computer = this.instantiationService.createInstance(ComputeAutomaticInstructions, this.input.currentModeKind, enabledTools, enabledSubAgents); await computer.collect(attachedContext, CancellationToken.None); } catch (err) { this.logService.error(`ChatWidget#_autoAttachInstructions: failed to compute automatic instructions`, err); diff --git a/src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts index bb38567ac0b10..c8d68f3eca364 100644 --- a/src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts @@ -1128,7 +1128,7 @@ export class ChatService extends Disposable implements IChatService { let collectedHooks: ChatRequestHooks | undefined; let hasDisabledClaudeHooks = false; try { - const hooksInfo = await this.promptsService.getHooks(token, model.sessionResource); + const hooksInfo = await this.promptsService.getHooks(token); if (hooksInfo) { collectedHooks = hooksInfo.hooks; hasDisabledClaudeHooks = hooksInfo.hasDisabledClaudeHooks; @@ -1141,7 +1141,7 @@ export class ChatService extends Disposable implements IChatService { const agentName = options?.modeInfo?.modeInstructions?.name; if (agentName) { try { - const agents = await this.promptsService.getCustomAgents(token, model.sessionResource); + const agents = await this.promptsService.getCustomAgents(token); const customAgent = agents.find(a => a.name === agentName); if (customAgent?.hooks) { collectedHooks = mergeHooks(collectedHooks, customAgent.hooks); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts index a095f3843d1b7..25b6bc272915b 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/computeAutomaticInstructions.ts @@ -67,7 +67,6 @@ export class ComputeAutomaticInstructions { private readonly _modeKind: ChatModeKind, private readonly _enabledTools: UserSelectedTools | undefined, private readonly _enabledSubagents: (readonly string[]) | undefined, - private readonly _sessionResource: URI | undefined, @IPromptsService private readonly _promptsService: IPromptsService, @ILogService public readonly _logService: ILogService, @ILabelService private readonly _labelService: ILabelService, @@ -99,7 +98,7 @@ export class ComputeAutomaticInstructions { public async collect(variables: ChatRequestVariableSet, token: CancellationToken): Promise { - const instructionFiles = await this._promptsService.getInstructionFiles(token, this._sessionResource); + const instructionFiles = await this._promptsService.getInstructionFiles(token); this._logService.trace(`[InstructionsContextComputer] ${instructionFiles.length} instruction files available.`); @@ -394,7 +393,7 @@ export class ComputeAutomaticInstructions { entries.push('', '', ''); // add trailing newline } - const agentSkills = await this._promptsService.findAgentSkills(token, this._sessionResource); + const agentSkills = await this._promptsService.findAgentSkills(token); // Filter out skills with disableModelInvocation=true (they can only be triggered manually via /name) // Also filter by `when` clause using the scoped context key service // Also filter out the troubleshoot skill when the feature flags are disabled @@ -458,7 +457,7 @@ export class ComputeAutomaticInstructions { return (agent: ICustomAgent) => subagents.includes(agent.name); } })(); - const agents = await this._promptsService.getCustomAgents(token, this._sessionResource); + const agents = await this._promptsService.getCustomAgents(token); if (agents.length > 0) { entries.push(''); entries.push('Here is a list of agents that can be used when running a subagent.'); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts index efe5182b6848c..445551bb288b5 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsService.ts @@ -451,9 +451,8 @@ export interface IPromptsService extends IDisposable { /** * Returns a prompt command if the command name is valid. - * @param sessionResource Optional session resource to scope debug logging to a specific session. */ - getPromptSlashCommands(token: CancellationToken, sessionResource?: URI): Promise; + getPromptSlashCommands(token: CancellationToken): Promise; /** * Returns the prompt command name for the given URI. @@ -472,9 +471,8 @@ export interface IPromptsService extends IDisposable { /** * Finds all available custom agents - * @param sessionResource Optional session resource to scope debug logging to a specific session. */ - getCustomAgents(token: CancellationToken, sessionResource?: URI): Promise; + getCustomAgents(token: CancellationToken): Promise; /** * Parses the provided URI @@ -532,9 +530,8 @@ export interface IPromptsService extends IDisposable { /** * Gets list of agent skills files. - * @param sessionResource Optional session resource to scope debug logging to a specific session. */ - findAgentSkills(token: CancellationToken, sessionResource?: URI): Promise; + findAgentSkills(token: CancellationToken): Promise; /** * Event that is triggered when the list of skills changes. @@ -544,15 +541,13 @@ export interface IPromptsService extends IDisposable { /** * Gets all hooks collected from hooks.json files. * The result is cached and invalidated when hook files change. - * @param sessionResource Optional session resource to scope debug logging to a specific session. */ - getHooks(token: CancellationToken, sessionResource?: URI): Promise; + getHooks(token: CancellationToken): Promise; /** * Gets all instruction files, logging discovery info to the debug log. - * @param sessionResource Optional session resource to scope debug logging to a specific session. */ - getInstructionFiles(token: CancellationToken, sessionResource?: URI): Promise; + getInstructionFiles(token: CancellationToken): Promise; /** * Returns the cached discovery info for the given prompt type. diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts index 90e2cf05a9d44..b6f70952812c7 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts @@ -590,7 +590,7 @@ export class PromptsService extends Disposable implements IPromptsService { return this.cachedSlashCommands.onDidChangePromise; } - public async getPromptSlashCommands(token: CancellationToken, _sessionResource?: URI): Promise { + public async getPromptSlashCommands(token: CancellationToken): Promise { const discoveryInfo = await this.cachedSlashCommands.get(token); const result = this.slashCommandsFromDiscoveryInfo(discoveryInfo); return result; @@ -703,7 +703,7 @@ export class PromptsService extends Disposable implements IPromptsService { return this.cachedInstructions.onDidChangePromise; } - public async getCustomAgents(token: CancellationToken, _sessionResource?: URI): Promise { + public async getCustomAgents(token: CancellationToken): Promise { const discoveryInfo = await this.cachedCustomAgents.get(token); const result = this.agentsFromDiscoveryInfo(discoveryInfo); return result; @@ -1112,7 +1112,7 @@ export class PromptsService extends Disposable implements IPromptsService { return this.cachedSkills.onDidChangePromise; } - public async findAgentSkills(token: CancellationToken, _sessionResource?: URI): Promise { + public async findAgentSkills(token: CancellationToken): Promise { const useAgentSkills = this.configurationService.getValue(PromptsConfig.USE_AGENT_SKILLS); if (!useAgentSkills) { return undefined; @@ -1251,7 +1251,7 @@ export class PromptsService extends Disposable implements IPromptsService { return { type: PromptsType.skill, files, sourceFolders, durationInMillis: stopWatch.elapsed() }; } - public async getHooks(token: CancellationToken, _sessionResource?: URI): Promise { + public async getHooks(token: CancellationToken): Promise { const discoveryInfo = await this.cachedHooks.get(token); const result = discoveryInfo.hooksInfo; return result; @@ -1272,7 +1272,7 @@ export class PromptsService extends Disposable implements IPromptsService { } } - public async getInstructionFiles(token: CancellationToken, _sessionResource?: URI): Promise { + public async getInstructionFiles(token: CancellationToken): Promise { const discoveryInfo = await this.cachedInstructions.get(token); const result = discoveryInfo.files.filter(file => file.status === 'loaded').map(file => file.promptPath); return result; diff --git a/src/vs/workbench/contrib/chat/common/tools/builtinTools/runSubagentTool.ts b/src/vs/workbench/contrib/chat/common/tools/builtinTools/runSubagentTool.ts index b74009f4a7d64..92b43a4993632 100644 --- a/src/vs/workbench/contrib/chat/common/tools/builtinTools/runSubagentTool.ts +++ b/src/vs/workbench/contrib/chat/common/tools/builtinTools/runSubagentTool.ts @@ -277,13 +277,13 @@ export class RunSubagentTool extends Disposable implements IToolImpl { } const variableSet = new ChatRequestVariableSet(); - const computer = this.instantiationService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, modeTools, undefined, invocation.context.sessionResource); + const computer = this.instantiationService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, modeTools, undefined); await computer.collect(variableSet, token); // Collect hooks from hook .json files let collectedHooks: ChatRequestHooks | undefined; try { - const info = await this.promptsService.getHooks(token, invocation.context.sessionResource); + const info = await this.promptsService.getHooks(token); collectedHooks = info?.hooks; } catch (error) { this.logService.warn('[ChatService] Failed to collect hooks:', error); diff --git a/src/vs/workbench/contrib/chat/test/common/chatService/chatService.test.ts b/src/vs/workbench/contrib/chat/test/common/chatService/chatService.test.ts index 8c11941ad8e9b..3c21b96cd5a02 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService/chatService.test.ts @@ -584,7 +584,7 @@ suite('ChatService', () => { test('disabled Claude hooks hint is shown once per workspace (fix for #295079)', async () => { // Set up a prompts service that reports disabled Claude hooks const mockPromptsService = new class extends MockPromptsService { - override getHooks(_token: CancellationToken, _sessionResource?: URI): Promise { + override getHooks(_token: CancellationToken): Promise { return Promise.resolve({ hooks: {}, hasDisabledClaudeHooks: true }); } }(); @@ -632,7 +632,7 @@ suite('ChatService', () => { // followed by the real resent request (with disabled hooks). const mockPromptsService = new class extends MockPromptsService { private _callCount = 0; - override getHooks(_token: CancellationToken, _sessionResource?: URI): Promise { + override getHooks(_token: CancellationToken): Promise { this._callCount++; // First call (setup agent): no disabled hooks // Second call (real request after resend): disabled hooks present diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/computeAutomaticInstructions.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/computeAutomaticInstructions.test.ts index 738828ed4c228..af0c6acb7cc0d 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/computeAutomaticInstructions.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/computeAutomaticInstructions.test.ts @@ -245,7 +245,7 @@ suite('ComputeAutomaticInstructions', () => { }, ]); { - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/file.ts'))); @@ -262,7 +262,7 @@ suite('ComputeAutomaticInstructions', () => { testConfigService.setUserConfiguration(PromptsConfig.INCLUDE_APPLYING_INSTRUCTIONS, false); testConfigService.setUserConfiguration(PromptsConfig.USE_COPILOT_INSTRUCTION_FILES, true); testConfigService.setUserConfiguration(PromptsConfig.USE_AGENT_MD, true); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/file.ts'))); @@ -279,7 +279,7 @@ suite('ComputeAutomaticInstructions', () => { testConfigService.setUserConfiguration(PromptsConfig.INCLUDE_APPLYING_INSTRUCTIONS, true); testConfigService.setUserConfiguration(PromptsConfig.USE_COPILOT_INSTRUCTION_FILES, false); testConfigService.setUserConfiguration(PromptsConfig.USE_AGENT_MD, true); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/file.ts'))); @@ -296,7 +296,7 @@ suite('ComputeAutomaticInstructions', () => { testConfigService.setUserConfiguration(PromptsConfig.INCLUDE_APPLYING_INSTRUCTIONS, true); testConfigService.setUserConfiguration(PromptsConfig.USE_COPILOT_INSTRUCTION_FILES, true); testConfigService.setUserConfiguration(PromptsConfig.USE_AGENT_MD, false); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/file.ts'))); @@ -346,7 +346,7 @@ suite('ComputeAutomaticInstructions', () => { }, ]); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/file.ts'))); @@ -381,7 +381,7 @@ suite('ComputeAutomaticInstructions', () => { }, ]); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Edit, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Edit, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/file.ts'))); @@ -416,7 +416,7 @@ suite('ComputeAutomaticInstructions', () => { }, ]); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/file.ts'))); @@ -458,7 +458,7 @@ suite('ComputeAutomaticInstructions', () => { }, ]); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/file.ts'))); @@ -494,7 +494,7 @@ suite('ComputeAutomaticInstructions', () => { }, ]); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/component.tsx'))); @@ -530,7 +530,7 @@ suite('ComputeAutomaticInstructions', () => { }, ]); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/file1.ts'))); variables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/file2.ts'))); @@ -564,7 +564,7 @@ suite('ComputeAutomaticInstructions', () => { }, ]); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/file.ts'))); @@ -596,7 +596,7 @@ suite('ComputeAutomaticInstructions', () => { }, ]); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/file.ts'))); @@ -644,7 +644,7 @@ suite('ComputeAutomaticInstructions', () => { }, ]); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/api/handler.ts'))); @@ -692,7 +692,7 @@ suite('ComputeAutomaticInstructions', () => { }, ]); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/component.tsx'))); @@ -732,7 +732,7 @@ suite('ComputeAutomaticInstructions', () => { }, ]); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'lib/utils.ts'))); @@ -779,7 +779,7 @@ suite('ComputeAutomaticInstructions', () => { const mainUri = URI.joinPath(rootFolderUri, '.github/instructions/main.instructions.md'); const referencedUri = URI.joinPath(rootFolderUri, '.github/instructions/referenced.instructions.md'); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/file.ts'))); @@ -815,7 +815,7 @@ suite('ComputeAutomaticInstructions', () => { const mainUri = URI.joinPath(rootFolderUri, '.github/instructions/main.instructions.md'); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/file.ts'))); @@ -864,7 +864,7 @@ suite('ComputeAutomaticInstructions', () => { const level2Uri = URI.joinPath(rootFolderUri, '.github/instructions/level2.instructions.md'); const level3Uri = URI.joinPath(rootFolderUri, '.github/instructions/level3.instructions.md'); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/file.ts'))); @@ -920,7 +920,7 @@ suite('ComputeAutomaticInstructions', () => { } as unknown as ITelemetryService; instaService.stub(ITelemetryService, mockTelemetryService); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/file.ts'))); @@ -977,7 +977,7 @@ suite('ComputeAutomaticInstructions', () => { } as unknown as ITelemetryService; instaService.stub(ITelemetryService, mockTelemetryService); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/file.ts'))); @@ -1023,7 +1023,7 @@ suite('ComputeAutomaticInstructions', () => { } as unknown as ITelemetryService; instaService.stub(ITelemetryService, mockTelemetryService); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/file.ts'))); @@ -1085,8 +1085,7 @@ suite('ComputeAutomaticInstructions', () => { ComputeAutomaticInstructions, ChatModeKind.Agent, { 'vscode_runSubagent': true }, - ['*'], - undefined + ['*'] ); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/file.ts'))); @@ -1145,7 +1144,6 @@ suite('ComputeAutomaticInstructions', () => { ChatModeKind.Agent, { 'vscode_readFile': true }, undefined, - undefined ); const variables = new ChatRequestVariableSet(); @@ -1215,7 +1213,6 @@ suite('ComputeAutomaticInstructions', () => { ChatModeKind.Agent, { 'vscode_readFile': true }, undefined, - undefined ); const variables = new ChatRequestVariableSet(); @@ -1260,7 +1257,6 @@ suite('ComputeAutomaticInstructions', () => { ComputeAutomaticInstructions, ChatModeKind.Agent, { 'vscode_readFile': true }, - undefined, undefined ); const variables = new ChatRequestVariableSet(); @@ -1327,7 +1323,6 @@ suite('ComputeAutomaticInstructions', () => { ComputeAutomaticInstructions, ChatModeKind.Agent, { 'vscode_readFile': true }, - undefined, undefined ); const variables = new ChatRequestVariableSet(); @@ -1395,7 +1390,6 @@ suite('ComputeAutomaticInstructions', () => { ComputeAutomaticInstructions, ChatModeKind.Agent, { 'vscode_readFile': true }, // Enable readFile tool - undefined, undefined ); const variables = new ChatRequestVariableSet(); @@ -1487,7 +1481,6 @@ suite('ComputeAutomaticInstructions', () => { ChatModeKind.Agent, { 'vscode_runSubagent': true }, // Enable runSubagent tool ['*'], // Enable all subagents - undefined ); const variables = new ChatRequestVariableSet(); @@ -1550,7 +1543,6 @@ suite('ComputeAutomaticInstructions', () => { ChatModeKind.Agent, { 'vscode_readFile': true }, // Enable readFile tool undefined, - undefined ); const variables = new ChatRequestVariableSet(); @@ -1601,7 +1593,6 @@ suite('ComputeAutomaticInstructions', () => { ChatModeKind.Agent, undefined, // No tools available undefined, - undefined ); const variables = new ChatRequestVariableSet(); @@ -1637,7 +1628,6 @@ suite('ComputeAutomaticInstructions', () => { ComputeAutomaticInstructions, ChatModeKind.Agent, { 'vscode_readFile': true }, // Enable readFile tool - undefined, undefined ); const variables = new ChatRequestVariableSet(); @@ -1692,7 +1682,6 @@ suite('ComputeAutomaticInstructions', () => { ChatModeKind.Agent, { 'vscode_readFile': true }, // Enable readFile tool undefined, - undefined ); const variables = new ChatRequestVariableSet(); @@ -1763,7 +1752,6 @@ suite('ComputeAutomaticInstructions', () => { ChatModeKind.Agent, { 'vscode_readFile': true }, // Enable readFile tool undefined, - undefined ); const variables = new ChatRequestVariableSet(); @@ -1803,7 +1791,7 @@ suite('ComputeAutomaticInstructions', () => { workspaceContextService.setWorkspace(testWorkspace(rootFolderUri)); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); await contextComputer.collect(variables, CancellationToken.None); @@ -1835,7 +1823,7 @@ suite('ComputeAutomaticInstructions', () => { }, ]); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/file.ts'))); @@ -1858,7 +1846,7 @@ suite('ComputeAutomaticInstructions', () => { }, ]); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/file.ts'))); @@ -1898,7 +1886,7 @@ suite('ComputeAutomaticInstructions', () => { // Test when USE_CLAUDE_MD is true testConfigService.setUserConfiguration(PromptsConfig.USE_CLAUDE_MD, true); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/file.ts'))); @@ -1910,7 +1898,7 @@ suite('ComputeAutomaticInstructions', () => { // Test when USE_CLAUDE_MD is false testConfigService.setUserConfiguration(PromptsConfig.USE_CLAUDE_MD, false); - const contextComputer2 = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer2 = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables2 = new ChatRequestVariableSet(); variables2.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/file.ts'))); @@ -1945,7 +1933,7 @@ suite('ComputeAutomaticInstructions', () => { // Test when USE_CLAUDE_MD is true testConfigService.setUserConfiguration(PromptsConfig.USE_CLAUDE_MD, true); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/file.ts'))); @@ -1957,7 +1945,7 @@ suite('ComputeAutomaticInstructions', () => { // Test when USE_CLAUDE_MD is false testConfigService.setUserConfiguration(PromptsConfig.USE_CLAUDE_MD, false); - const contextComputer2 = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer2 = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables2 = new ChatRequestVariableSet(); variables2.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/file.ts'))); @@ -2000,7 +1988,7 @@ suite('ComputeAutomaticInstructions', () => { await workspaceTrustService.setTrustedUris([URI.file(parentFolder)]); - const disabledParentContextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const disabledParentContextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const disabledParentVariables = new ChatRequestVariableSet(); disabledParentVariables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/file.ts'))); @@ -2015,7 +2003,7 @@ suite('ComputeAutomaticInstructions', () => { // Parent folder settings should allow finding both root and .claude CLAUDE files above the workspace folder. testConfigService.setUserConfiguration(PromptsConfig.USE_CUSTOMIZATIONS_IN_PARENT_REPOS, true); - const enabledParentContextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const enabledParentContextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const enabledParentVariables = new ChatRequestVariableSet(); enabledParentVariables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/file.ts'))); @@ -2061,7 +2049,7 @@ suite('ComputeAutomaticInstructions', () => { await workspaceTrustService.setTrustedUris([URI.file(parentFolder)]); - const disabledParentContextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const disabledParentContextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const disabledParentVariables = new ChatRequestVariableSet(); disabledParentVariables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/file.ts'))); @@ -2075,7 +2063,7 @@ suite('ComputeAutomaticInstructions', () => { testConfigService.setUserConfiguration(PromptsConfig.USE_CUSTOMIZATIONS_IN_PARENT_REPOS, true); - const enabledParentContextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const enabledParentContextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const enabledParentVariables = new ChatRequestVariableSet(); enabledParentVariables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/file.ts'))); @@ -2112,7 +2100,7 @@ suite('ComputeAutomaticInstructions', () => { // Test when USE_CLAUDE_MD is true testConfigService.setUserConfiguration(PromptsConfig.USE_CLAUDE_MD, true); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/file.ts'))); @@ -2124,7 +2112,7 @@ suite('ComputeAutomaticInstructions', () => { // Test when USE_CLAUDE_MD is false testConfigService.setUserConfiguration(PromptsConfig.USE_CLAUDE_MD, false); - const contextComputer2 = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer2 = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables2 = new ChatRequestVariableSet(); variables2.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/file.ts'))); @@ -2175,7 +2163,7 @@ suite('ComputeAutomaticInstructions', () => { }, ]); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolder1Uri, 'src/file.ts'))); variables.add(toFileVariableEntry(URI.joinPath(rootFolder2Uri, 'src/file.js'))); @@ -2222,7 +2210,7 @@ suite('ComputeAutomaticInstructions', () => { // Test when USE_CLAUDE_MD is true testConfigService.setUserConfiguration(PromptsConfig.USE_CLAUDE_MD, true); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolder1Uri, 'src/file.ts'))); variables.add(toFileVariableEntry(URI.joinPath(rootFolder2Uri, 'src/file.js'))); @@ -2268,7 +2256,7 @@ suite('ComputeAutomaticInstructions', () => { // Test when USE_CLAUDE_MD is true testConfigService.setUserConfiguration(PromptsConfig.USE_CLAUDE_MD, true); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolder1Uri, 'src/file.ts'))); variables.add(toFileVariableEntry(URI.joinPath(rootFolder2Uri, 'src/file.js'))); @@ -2322,7 +2310,7 @@ suite('ComputeAutomaticInstructions', () => { // Test when USE_CLAUDE_MD is true testConfigService.setUserConfiguration(PromptsConfig.USE_CLAUDE_MD, true); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolder1Uri, 'src/file.ts'))); variables.add(toFileVariableEntry(URI.joinPath(rootFolder2Uri, 'src/file.js'))); @@ -2370,7 +2358,7 @@ suite('ComputeAutomaticInstructions', () => { // Test when USE_CLAUDE_MD is false testConfigService.setUserConfiguration(PromptsConfig.USE_CLAUDE_MD, false); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolder1Uri, 'src/file.ts'))); variables.add(toFileVariableEntry(URI.joinPath(rootFolder2Uri, 'src/file.js'))); @@ -2424,7 +2412,7 @@ suite('ComputeAutomaticInstructions', () => { // Test when USE_CLAUDE_MD is true testConfigService.setUserConfiguration(PromptsConfig.USE_CLAUDE_MD, true); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolder1Uri, 'src/file.ts'))); variables.add(toFileVariableEntry(URI.joinPath(rootFolder2Uri, 'src/file.js'))); @@ -2480,7 +2468,7 @@ suite('ComputeAutomaticInstructions', () => { testConfigService.setUserConfiguration(PromptsConfig.USE_AGENT_MD, true); testConfigService.setUserConfiguration(PromptsConfig.USE_CLAUDE_MD, true); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const variables = new ChatRequestVariableSet(); variables.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'src/file.ts'))); diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/mockPromptsService.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/mockPromptsService.ts index 441a7dad9fbcf..b2505511094fc 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/mockPromptsService.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/mockPromptsService.ts @@ -28,7 +28,7 @@ export class MockPromptsService implements IPromptsService { this._onDidChangeCustomAgents.fire(); } - async getCustomAgents(token: CancellationToken, sessionResource?: URI): Promise { + async getCustomAgents(token: CancellationToken): Promise { return this._customModes; } @@ -47,7 +47,7 @@ export class MockPromptsService implements IPromptsService { resolvePromptSlashCommand(command: string, _token: CancellationToken): Promise { throw new Error('Not implemented'); } get onDidChangeSlashCommands(): Event { throw new Error('Not implemented'); } // eslint-disable-next-line @typescript-eslint/no-explicit-any - getPromptSlashCommands(_token: CancellationToken, _sessionResource?: URI): Promise { throw new Error('Not implemented'); } + getPromptSlashCommands(_token: CancellationToken): Promise { throw new Error('Not implemented'); } getPromptSlashCommandName(uri: URI, _token: CancellationToken): Promise { throw new Error('Not implemented'); } // eslint-disable-next-line @typescript-eslint/no-explicit-any parse(_uri: URI, _type: any, _token: CancellationToken): Promise { throw new Error('Not implemented'); } @@ -62,10 +62,10 @@ export class MockPromptsService implements IPromptsService { getDisabledPromptFiles(type: PromptsType): ResourceSet { throw new Error('Method not implemented.'); } setDisabledPromptFiles(type: PromptsType, uris: ResourceSet): void { throw new Error('Method not implemented.'); } registerPromptFileProvider(extension: IExtensionDescription, type: PromptsType, provider: { providePromptFiles: (context: IPromptFileContext, token: CancellationToken) => Promise }): IDisposable { throw new Error('Method not implemented.'); } - findAgentSkills(token: CancellationToken, sessionResource?: URI): Promise { throw new Error('Method not implemented.'); } + findAgentSkills(token: CancellationToken): Promise { throw new Error('Method not implemented.'); } // eslint-disable-next-line @typescript-eslint/no-explicit-any - getHooks(_token: CancellationToken, _sessionResource?: URI): Promise { throw new Error('Method not implemented.'); } - getInstructionFiles(_token: CancellationToken, _sessionResource?: URI): Promise { throw new Error('Method not implemented.'); } + getHooks(_token: CancellationToken): Promise { throw new Error('Method not implemented.'); } + getInstructionFiles(_token: CancellationToken): Promise { throw new Error('Method not implemented.'); } getDiscoveryInfo(_type: PromptsType, _token: CancellationToken): Promise { throw new Error('Method not implemented.'); } dispose(): void { } onDidChangeInstructions: Event = Event.None; diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts index 2fa737d14d3e0..4e3f672afced5 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts @@ -3936,7 +3936,7 @@ suite('PromptsService', () => { testPluginsObservable.set([plugin], undefined); - const result = await service.getHooks(CancellationToken.None, URI.file('/session')); + const result = await service.getHooks(CancellationToken.None); const capturedDiscoveryInfo = await service.getDiscoveryInfo(PromptsType.hook, CancellationToken.None); assert.ok(result, 'Expected hooks result with plugin hooks'); From f63bcab88b3b5ceb70e17608300ed51f8375ec96 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 1 Apr 2026 16:24:34 +0200 Subject: [PATCH 3/6] add IChatAgentService.onWillInvokeAgent --- .../chat/browser/promptsDebugContribution.ts | 8 ++++---- .../chat/common/participants/chatAgents.ts | 9 +++++++++ .../browser/promptsDebugContribution.test.ts | 18 +++++++++--------- .../service/promptsService.test.ts | 6 +++--- .../chat/test/common/voiceChatService.test.ts | 1 + 5 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/promptsDebugContribution.ts b/src/vs/workbench/contrib/chat/browser/promptsDebugContribution.ts index b44ee4aa4d411..e62ccb659acab 100644 --- a/src/vs/workbench/contrib/chat/browser/promptsDebugContribution.ts +++ b/src/vs/workbench/contrib/chat/browser/promptsDebugContribution.ts @@ -10,7 +10,7 @@ import { localize } from '../../../../nls.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { IWorkbenchContribution } from '../../../common/contributions.js'; import { IChatDebugResolvedEventContent, IChatDebugService } from '../common/chatDebugService.js'; -import { IChatService } from '../common/chatService/chatService.js'; +import { IChatAgentService } from '../common/participants/chatAgents.js'; import { PromptsType } from '../common/promptSyntax/promptTypes.js'; import { IHookDiscoveryInfo, IPromptDiscoveryInfo, IPromptsService } from '../common/promptSyntax/service/promptsService.js'; @@ -31,15 +31,15 @@ export class PromptsDebugContribution extends Disposable implements IWorkbenchCo constructor( @IPromptsService private readonly promptsService: IPromptsService, - @IChatService chatService: IChatService, + @IChatAgentService chatAgentService: IChatAgentService, @IChatDebugService chatDebugService: IChatDebugService, @ILogService logService: ILogService, ) { super(); // Forward discovery log events to the debug service. - this._register(chatService.onDidSubmitRequest(async chatRequest => { - const sessionResource = chatRequest.chatSessionResource; + this._register(chatAgentService.onWillInvokeAgent(async e => { + const sessionResource = e.request.sessionResource; const cts = new CancellationTokenSource(); try { diff --git a/src/vs/workbench/contrib/chat/common/participants/chatAgents.ts b/src/vs/workbench/contrib/chat/common/participants/chatAgents.ts index 80728aae75a7d..89b62358fa70a 100644 --- a/src/vs/workbench/contrib/chat/common/participants/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/participants/chatAgents.ts @@ -218,12 +218,18 @@ export interface IChatAgentCompletionItem { command?: Command; } +export interface IChatAgentInvocationEvent { + readonly agentId: string; + readonly request: Readonly; +} + export interface IChatAgentService { _serviceBrand: undefined; /** * undefined when an agent was removed */ readonly onDidChangeAgents: Event; + readonly onWillInvokeAgent: Event; readonly hasToolsAgent: boolean; registerAgent(id: string, data: IChatAgentData): IDisposable; registerAgentImplementation(id: string, agent: IChatAgentImplementation): IDisposable; @@ -268,6 +274,8 @@ export class ChatAgentService extends Disposable implements IChatAgentService { private readonly _onDidChangeAgents = this._register(new Emitter()); readonly onDidChangeAgents: Event = this._onDidChangeAgents.event; + private readonly _onWillInvokeAgent = this._register(new Emitter()); + readonly onWillInvokeAgent: Event = this._onWillInvokeAgent.event; private readonly _agentsContextKeys = new Set(); private readonly _hasDefaultAgent: IContextKey; @@ -514,6 +522,7 @@ export class ChatAgentService extends Disposable implements IChatAgentService { throw new Error(`No activated agent with id "${id}"`); } + this._onWillInvokeAgent.fire({ agentId: id, request }); const result = await data.impl.invoke(request, progress, history, token); markChat(request.sessionResource, ChatPerfMark.AgentDidInvoke); return result; diff --git a/src/vs/workbench/contrib/chat/test/browser/promptsDebugContribution.test.ts b/src/vs/workbench/contrib/chat/test/browser/promptsDebugContribution.test.ts index 18cd019d3ce02..9dc6a3b700cf9 100644 --- a/src/vs/workbench/contrib/chat/test/browser/promptsDebugContribution.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/promptsDebugContribution.test.ts @@ -9,9 +9,9 @@ import { URI } from '../../../../../base/common/uri.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; import { ChatDebugLogLevel, IChatDebugEvent, IChatDebugGenericEvent, IChatDebugService } from '../../common/chatDebugService.js'; -import { IChatService } from '../../common/chatService/chatService.js'; import { ChatDebugServiceImpl } from '../../common/chatDebugServiceImpl.js'; import { LocalChatSessionUri } from '../../common/model/chatUri.js'; +import { IChatAgentService, IChatAgentInvocationEvent } from '../../common/participants/chatAgents.js'; import { PromptsDebugContribution } from '../../browser/promptsDebugContribution.js'; import { ILocalPromptPath, IPromptDiscoveryInfo, IPromptsService, PromptsStorage } from '../../common/promptSyntax/service/promptsService.js'; import { PromptsType } from '../../common/promptSyntax/promptTypes.js'; @@ -37,7 +37,7 @@ suite('PromptsDebugContribution', () => { const disposables = ensureNoDisposablesAreLeakedInTestSuite(); let chatDebugService: ChatDebugServiceImpl; - let submitRequestEmitter: Emitter<{ readonly chatSessionResource: URI }>; + let willInvokeAgentEmitter: Emitter; let instaService: TestInstantiationService; let promptsService: Partial; const emptyDiscoveryInfo = (type: PromptsType): IPromptDiscoveryInfo => ({ type, files: [], durationInMillis: 0 }); @@ -48,8 +48,8 @@ suite('PromptsDebugContribution', () => { chatDebugService = disposables.add(new ChatDebugServiceImpl()); instaService.stub(IChatDebugService, chatDebugService); - submitRequestEmitter = disposables.add(new Emitter<{ readonly chatSessionResource: URI }>()); - instaService.stub(IChatService, { onDidSubmitRequest: submitRequestEmitter.event } as Partial); + willInvokeAgentEmitter = disposables.add(new Emitter()); + instaService.stub(IChatAgentService, { onWillInvokeAgent: willInvokeAgentEmitter.event } as Partial); promptsService = { getDiscoveryInfo: async type => emptyDiscoveryInfo(type), }; @@ -71,7 +71,7 @@ suite('PromptsDebugContribution', () => { }] : [], }); - submitRequestEmitter.fire({ chatSessionResource: LocalChatSessionUri.forSession('session-1') }); + willInvokeAgentEmitter.fire({ agentId: 'test-agent', request: { sessionResource: LocalChatSessionUri.forSession('session-1') } as IChatAgentInvocationEvent['request'] }); await flushAsyncLogging(); assert.strictEqual(firedEvents.length, 5); @@ -104,7 +104,7 @@ suite('PromptsDebugContribution', () => { }; promptsService.getDiscoveryInfo = async type => type === PromptsType.instructions ? discoveryInfo : emptyDiscoveryInfo(type); - submitRequestEmitter.fire({ chatSessionResource: LocalChatSessionUri.forSession('session-1') }); + willInvokeAgentEmitter.fire({ agentId: 'test-agent', request: { sessionResource: LocalChatSessionUri.forSession('session-1') } as IChatAgentInvocationEvent['request'] }); await flushAsyncLogging(); const instructionsEvent = firedEvents.find((e): e is IChatDebugGenericEvent => isGenericEvent(e) && e.name === 'Load Instructions'); @@ -139,7 +139,7 @@ suite('PromptsDebugContribution', () => { disposables.add(chatDebugService.onDidAddEvent(e => firedEvents.push(e))); promptsService.getDiscoveryInfo = async type => emptyDiscoveryInfo(type); - submitRequestEmitter.fire({ chatSessionResource: LocalChatSessionUri.forSession('session-1') }); + willInvokeAgentEmitter.fire({ agentId: 'test-agent', request: { sessionResource: LocalChatSessionUri.forSession('session-1') } as IChatAgentInvocationEvent['request'] }); await flushAsyncLogging(); assert.strictEqual(firedEvents.length, 5); @@ -169,7 +169,7 @@ suite('PromptsDebugContribution', () => { }; promptsService.getDiscoveryInfo = async type => type === PromptsType.instructions ? discoveryInfo : emptyDiscoveryInfo(type); - submitRequestEmitter.fire({ chatSessionResource: LocalChatSessionUri.forSession('session-1') }); + willInvokeAgentEmitter.fire({ agentId: 'test-agent', request: { sessionResource: LocalChatSessionUri.forSession('session-1') } as IChatAgentInvocationEvent['request'] }); await flushAsyncLogging(); const eventId = firedEvents.find((e): e is IChatDebugGenericEvent => isGenericEvent(e) && e.name === 'Load Instructions')!.id!; @@ -190,7 +190,7 @@ suite('PromptsDebugContribution', () => { disposables.add(chatDebugService.onDidAddEvent(e => firedEvents.push(e))); promptsService.getDiscoveryInfo = async type => emptyDiscoveryInfo(type); - submitRequestEmitter.fire({ chatSessionResource: LocalChatSessionUri.forSession('session-1') }); + willInvokeAgentEmitter.fire({ agentId: 'test-agent', request: { sessionResource: LocalChatSessionUri.forSession('session-1') } as IChatAgentInvocationEvent['request'] }); await flushAsyncLogging(); const event = firedEvents[0] as IChatDebugGenericEvent; diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts index 4e3f672afced5..e1e6ab8f07163 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptsService.test.ts @@ -492,7 +492,7 @@ suite('PromptsService', () => { ]); const instructionFiles = await service.listPromptFiles(PromptsType.instructions, CancellationToken.None); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const context = { files: new ResourceSet([ URI.joinPath(rootFolderUri, 'folder1/main.tsx'), @@ -663,7 +663,7 @@ suite('PromptsService', () => { ]); const instructionFiles = await service.listPromptFiles(PromptsType.instructions, CancellationToken.None); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const context = { files: new ResourceSet([ URI.joinPath(rootFolderUri, 'folder1/main.tsx'), @@ -737,7 +737,7 @@ suite('PromptsService', () => { ]); - const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined, undefined); + const contextComputer = instaService.createInstance(ComputeAutomaticInstructions, ChatModeKind.Agent, undefined, undefined); const context = new ChatRequestVariableSet(); context.add(toFileVariableEntry(URI.joinPath(rootFolderUri, 'README.md'))); diff --git a/src/vs/workbench/contrib/chat/test/common/voiceChatService.test.ts b/src/vs/workbench/contrib/chat/test/common/voiceChatService.test.ts index fa3bc455cb15e..26335126c145c 100644 --- a/src/vs/workbench/contrib/chat/test/common/voiceChatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/voiceChatService.test.ts @@ -68,6 +68,7 @@ suite('VoiceChat', () => { class TestChatAgentService implements IChatAgentService { _serviceBrand: undefined; readonly onDidChangeAgents = Event.None; + readonly onWillInvokeAgent = Event.None; registerAgentImplementation(id: string, agent: IChatAgentImplementation): IDisposable { throw new Error(); } registerDynamicAgent(data: IChatAgentData, agentImpl: IChatAgentImplementation): IDisposable { throw new Error('Method not implemented.'); } invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress[]) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise { throw new Error(); } From cae137b38bbf6e3782a323850ab8084de24447b2 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 1 Apr 2026 16:32:22 +0200 Subject: [PATCH 4/6] remove async --- .../contrib/chat/browser/promptsDebugContribution.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/promptsDebugContribution.ts b/src/vs/workbench/contrib/chat/browser/promptsDebugContribution.ts index e62ccb659acab..4af54ad02887e 100644 --- a/src/vs/workbench/contrib/chat/browser/promptsDebugContribution.ts +++ b/src/vs/workbench/contrib/chat/browser/promptsDebugContribution.ts @@ -45,7 +45,7 @@ export class PromptsDebugContribution extends Disposable implements IWorkbenchCo try { for (const promptType of [PromptsType.agent, PromptsType.instructions, PromptsType.prompt, PromptsType.skill, PromptsType.hook]) { const discoveryInfo = await this.promptsService.getDiscoveryInfo(promptType, cts.token); - const { name, details } = await this.getDiscoveryLogEntry(promptType, discoveryInfo); + const { name, details } = this.getDiscoveryLogEntry(promptType, discoveryInfo); const eventId = generateUuid(); this._discoveryEventDetails.set(eventId, discoveryInfo); @@ -108,7 +108,7 @@ export class PromptsDebugContribution extends Disposable implements IWorkbenchCo })); } - private async getDiscoveryLogEntry(promptType: PromptsType, discoveryInfo: IPromptDiscoveryInfo): Promise<{ readonly name: string; readonly details?: string }> { + private getDiscoveryLogEntry(promptType: PromptsType, discoveryInfo: IPromptDiscoveryInfo): { readonly name: string; readonly details?: string } { const durationInMillis = discoveryInfo.durationInMillis; const loadedCount = discoveryInfo.files.filter(file => file.status === 'loaded').length; From 7fa6deee69faf24b2d47ad07dad67553f99ab101 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 1 Apr 2026 16:43:31 +0200 Subject: [PATCH 5/6] update --- .../contrib/chat/browser/promptsDebugContribution.ts | 10 +++++----- .../contrib/chat/common/chatService/chatService.ts | 2 +- .../contrib/chat/common/chatService/chatServiceImpl.ts | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/promptsDebugContribution.ts b/src/vs/workbench/contrib/chat/browser/promptsDebugContribution.ts index 4af54ad02887e..a279a2341ea4b 100644 --- a/src/vs/workbench/contrib/chat/browser/promptsDebugContribution.ts +++ b/src/vs/workbench/contrib/chat/browser/promptsDebugContribution.ts @@ -43,9 +43,9 @@ export class PromptsDebugContribution extends Disposable implements IWorkbenchCo const cts = new CancellationTokenSource(); try { - for (const promptType of [PromptsType.agent, PromptsType.instructions, PromptsType.prompt, PromptsType.skill, PromptsType.hook]) { - const discoveryInfo = await this.promptsService.getDiscoveryInfo(promptType, cts.token); - const { name, details } = this.getDiscoveryLogEntry(promptType, discoveryInfo); + const discoveryInfos = await Promise.all([PromptsType.agent, PromptsType.instructions, PromptsType.prompt, PromptsType.skill, PromptsType.hook].map(type => this.promptsService.getDiscoveryInfo(type, cts.token))); + for (const discoveryInfo of discoveryInfos) { + const { name, details } = this.getDiscoveryLogEntry(discoveryInfo); const eventId = generateUuid(); this._discoveryEventDetails.set(eventId, discoveryInfo); @@ -108,13 +108,13 @@ export class PromptsDebugContribution extends Disposable implements IWorkbenchCo })); } - private getDiscoveryLogEntry(promptType: PromptsType, discoveryInfo: IPromptDiscoveryInfo): { readonly name: string; readonly details?: string } { + private getDiscoveryLogEntry(discoveryInfo: IPromptDiscoveryInfo): { readonly name: string; readonly details?: string } { const durationInMillis = discoveryInfo.durationInMillis; const loadedCount = discoveryInfo.files.filter(file => file.status === 'loaded').length; const skippedCount = discoveryInfo.files.length - loadedCount; - switch (promptType) { + switch (discoveryInfo.type) { case PromptsType.prompt: return { name: localize('promptsService.loadSlashCommands', 'Load Slash Commands'), diff --git a/src/vs/workbench/contrib/chat/common/chatService/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService/chatService.ts index 5000b8fc79300..79201b41d44bb 100644 --- a/src/vs/workbench/contrib/chat/common/chatService/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService/chatService.ts @@ -1407,7 +1407,7 @@ export interface IChatService { _serviceBrand: undefined; transferredSessionResource: URI | undefined; - readonly onDidSubmitRequest: Event<{ readonly chatSessionResource: URI; readonly message?: IParsedChatRequest; readonly options?: Readonly }>; + readonly onDidSubmitRequest: Event<{ readonly chatSessionResource: URI; readonly message?: IParsedChatRequest }>; readonly onDidCreateModel: Event; diff --git a/src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts index c8d68f3eca364..34dcc58ebd841 100644 --- a/src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts @@ -114,7 +114,7 @@ export class ChatService extends Disposable implements IChatService { return this._transferredSessionResource; } - private readonly _onDidSubmitRequest = this._register(new Emitter<{ readonly chatSessionResource: URI; readonly message?: IParsedChatRequest; readonly options?: Readonly }>()); + private readonly _onDidSubmitRequest = this._register(new Emitter<{ readonly chatSessionResource: URI; readonly message?: IParsedChatRequest }>()); public readonly onDidSubmitRequest = this._onDidSubmitRequest.event; public get onDidCreateModel() { return this._sessionModels.onDidCreateModel; } @@ -1427,7 +1427,7 @@ export class ChatService extends Disposable implements IChatService { if (options?.userSelectedModelId) { this.languageModelsService.addToRecentlyUsedList(options.userSelectedModelId); } - this._onDidSubmitRequest.fire({ chatSessionResource: model.sessionResource, message: parsedRequest, options: options }); + this._onDidSubmitRequest.fire({ chatSessionResource: model.sessionResource, message: parsedRequest }); return { responseCreatedPromise: responseCreated.p, responseCompletePromise: rawResponsePromise, From 538c9b0d930315c6052d3702e43c5767ea88e956 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 1 Apr 2026 16:50:24 +0200 Subject: [PATCH 6/6] .toFixed(1) --- .../workbench/contrib/chat/browser/promptsDebugContribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/promptsDebugContribution.ts b/src/vs/workbench/contrib/chat/browser/promptsDebugContribution.ts index a279a2341ea4b..bb19c580490d3 100644 --- a/src/vs/workbench/contrib/chat/browser/promptsDebugContribution.ts +++ b/src/vs/workbench/contrib/chat/browser/promptsDebugContribution.ts @@ -110,7 +110,7 @@ export class PromptsDebugContribution extends Disposable implements IWorkbenchCo private getDiscoveryLogEntry(discoveryInfo: IPromptDiscoveryInfo): { readonly name: string; readonly details?: string } { - const durationInMillis = discoveryInfo.durationInMillis; + const durationInMillis = discoveryInfo.durationInMillis.toFixed(1); const loadedCount = discoveryInfo.files.filter(file => file.status === 'loaded').length; const skippedCount = discoveryInfo.files.length - loadedCount;