Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
57e647c
wip
pwang347 Dec 1, 2025
241e775
updates
pwang347 Dec 1, 2025
424a1db
Apply suggestion from @Copilot
pwang347 Dec 1, 2025
dc9551e
test
pwang347 Dec 1, 2025
696ef50
dispose
pwang347 Dec 1, 2025
2441746
clean
pwang347 Dec 1, 2025
112dc09
remove
pwang347 Dec 1, 2025
25cd745
invalidate
pwang347 Dec 1, 2025
1ca3933
Merge branch 'main' of github.com:microsoft/vscode into pawang/orgIns…
pwang347 Dec 31, 2025
1600cdf
wip
pwang347 Dec 31, 2025
88bc61c
pr
pwang347 Dec 31, 2025
d31d7ba
use enum
pwang347 Dec 31, 2025
4dd999d
Merge branch 'main' of github.com:microsoft/vscode into pawang/orgIns…
pwang347 Jan 1, 2026
6320214
Merge branch 'main' of github.com:microsoft/vscode into pawang/orgIns…
pwang347 Jan 7, 2026
3ce36e7
PR
pwang347 Jan 7, 2026
5a44074
clean
pwang347 Jan 7, 2026
846facc
more cleanup
pwang347 Jan 7, 2026
de3f04c
more cleanup
pwang347 Jan 7, 2026
bc3dc9e
more cleanup
pwang347 Jan 7, 2026
50f719a
more cleanup
pwang347 Jan 7, 2026
d334ac1
more cleanup
pwang347 Jan 7, 2026
057840b
nit
pwang347 Jan 7, 2026
d283548
add optional metadata
pwang347 Jan 7, 2026
36346df
use new proposal
pwang347 Jan 7, 2026
2617d68
clean
pwang347 Jan 7, 2026
44f5501
clean
pwang347 Jan 7, 2026
f4e9bc4
nit
pwang347 Jan 7, 2026
a5eec28
Update src/vs/workbench/api/common/extHostChatAgents2.ts
pwang347 Jan 8, 2026
c50fcb8
PR
pwang347 Jan 8, 2026
f07a636
Merge branch 'pawang/orgInstructions' of github.com:microsoft/vscode …
pwang347 Jan 8, 2026
1c07b6e
use type instead of 'in' checks
aeschli Jan 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion src/vs/workbench/api/browser/mainThreadChatAgents2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { IUriIdentityService } from '../../../platform/uriIdentity/common/uriIde
import { IChatWidgetService } from '../../contrib/chat/browser/chat.js';
import { AddDynamicVariableAction, IAddDynamicVariableContext } from '../../contrib/chat/browser/contrib/chatDynamicVariables.js';
import { IChatAgentHistoryEntry, IChatAgentImplementation, IChatAgentRequest, IChatAgentService } from '../../contrib/chat/common/chatAgents.js';
import { ICustomAgentQueryOptions, IPromptsService } from '../../contrib/chat/common/promptSyntax/service/promptsService.js';
import { ICustomAgentQueryOptions, IPromptsService, IInstructionQueryOptions } from '../../contrib/chat/common/promptSyntax/service/promptsService.js';
import { IChatEditingService, IChatRelatedFileProviderMetadata } from '../../contrib/chat/common/chatEditingService.js';
import { IChatModel } from '../../contrib/chat/common/chatModel.js';
import { ChatRequestAgentPart } from '../../contrib/chat/common/chatParserTypes.js';
Expand Down Expand Up @@ -98,6 +98,8 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA

private readonly _customAgentsProviders = this._register(new DisposableMap<number, IDisposable>());
private readonly _customAgentsProviderEmitters = this._register(new DisposableMap<number, Emitter<void>>());
private readonly _instructionsProviders = this._register(new DisposableMap<number, IDisposable>());
private readonly _instructionsProviderEmitters = this._register(new DisposableMap<number, Emitter<void>>());

private readonly _pendingProgress = new Map<string, { progress: (parts: IChatProgress[]) => void; chatSession: IChatModel | undefined }>();
private readonly _proxy: ExtHostChatAgentsShape2;
Expand Down Expand Up @@ -468,6 +470,46 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA
emitter.fire();
}
}

async $registerInstructionsProvider(handle: number, extensionId: ExtensionIdentifier): Promise<void> {
const extension = await this._extensionService.getExtension(extensionId.value);
if (!extension) {
this._logService.error(`[MainThreadChatAgents2] Could not find extension for InstructionsProvider: ${extensionId.value}`);
return;
}

const emitter = new Emitter<void>();
this._instructionsProviderEmitters.set(handle, emitter);

const disposable = this._promptsService.registerInstructionsProvider(extension, {
onDidChangeInstructions: emitter.event,
provideInstructions: async (options: IInstructionQueryOptions, token: CancellationToken) => {
const instructions = await this._proxy.$provideInstructions(handle, options, token);
if (!instructions) {
return undefined;
}
// Convert UriComponents to URI
return instructions.map(instruction => ({
...instruction,
uri: URI.revive(instruction.uri)
}));
}
});

this._instructionsProviders.set(handle, disposable);
}

$unregisterInstructionsProvider(handle: number): void {
this._instructionsProviders.deleteAndDispose(handle);
this._instructionsProviderEmitters.deleteAndDispose(handle);
}

$onDidChangeInstructions(handle: number): void {
const emitter = this._instructionsProviderEmitters.get(handle);
if (emitter) {
emitter.fire();
}
}
}


Expand Down
4 changes: 4 additions & 0 deletions src/vs/workbench/api/common/extHost.api.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1545,6 +1545,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
checkProposedApiEnabled(extension, 'chatParticipantPrivate');
return extHostChatAgents2.registerCustomAgentsProvider(extension, provider);
},
registerInstructionsProvider(provider: vscode.InstructionsProvider): vscode.Disposable {
checkProposedApiEnabled(extension, 'chatParticipantPrivate');
return extHostChatAgents2.registerInstructionsProvider(extension, provider);
},
};

// namespace: lm
Expand Down
8 changes: 6 additions & 2 deletions src/vs/workbench/api/common/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ import { IChatRequestVariableValue } from '../../contrib/chat/common/chatVariabl
import { ChatAgentLocation } from '../../contrib/chat/common/constants.js';
import { IChatMessage, IChatResponsePart, ILanguageModelChatMetadataAndIdentifier, ILanguageModelChatSelector } from '../../contrib/chat/common/languageModels.js';
import { IPreparedToolInvocation, IToolInvocation, IToolInvocationPreparationContext, IToolProgressStep, IToolResult, ToolDataSource } from '../../contrib/chat/common/languageModelToolsService.js';
import { ICustomAgentQueryOptions, IExternalCustomAgent } from '../../contrib/chat/common/promptSyntax/service/promptsService.js';
import { ICustomAgentQueryOptions, IExternalCustomAgentResource, IInstructionQueryOptions } from '../../contrib/chat/common/promptSyntax/service/promptsService.js';
import { DebugConfigurationProviderTriggerKind, IAdapterDescriptor, IConfig, IDebugSessionReplMode, IDebugTestRunReference, IDebugVisualization, IDebugVisualizationContext, IDebugVisualizationTreeItem, MainThreadDebugVisualization } from '../../contrib/debug/common/debug.js';
import { McpCollectionDefinition, McpConnectionState, McpServerDefinition, McpServerLaunch } from '../../contrib/mcp/common/mcpTypes.js';
import * as notebookCommon from '../../contrib/notebook/common/notebookCommon.js';
Expand Down Expand Up @@ -1397,6 +1397,9 @@ export interface MainThreadChatAgentsShape2 extends IChatAgentProgressShape, IDi
$registerCustomAgentsProvider(handle: number, extension: ExtensionIdentifier): void;
$unregisterCustomAgentsProvider(handle: number): void;
$onDidChangeCustomAgents(handle: number): void;
$registerInstructionsProvider(handle: number, extension: ExtensionIdentifier): void;
$unregisterInstructionsProvider(handle: number): void;
$onDidChangeInstructions(handle: number): void;
$registerAgentCompletionsProvider(handle: number, id: string, triggerCharacters: string[]): void;
$unregisterAgentCompletionsProvider(handle: number, id: string): void;
$updateAgent(handle: number, metadataUpdate: IExtensionChatAgentMetadata): void;
Expand Down Expand Up @@ -1461,7 +1464,8 @@ export interface ExtHostChatAgentsShape2 {
$releaseSession(sessionResource: UriComponents): void;
$detectChatParticipant(handle: number, request: Dto<IChatAgentRequest>, context: { history: IChatAgentHistoryEntryDto[] }, options: { participants: IChatParticipantMetadata[]; location: ChatAgentLocation }, token: CancellationToken): Promise<IChatParticipantDetectionResult | null | undefined>;
$provideRelatedFiles(handle: number, request: Dto<IChatRequestDraft>, token: CancellationToken): Promise<Dto<IChatRelatedFile>[] | undefined>;
$provideCustomAgents(handle: number, options: ICustomAgentQueryOptions, token: CancellationToken): Promise<Dto<IExternalCustomAgent>[] | undefined>;
$provideCustomAgents(handle: number, options: ICustomAgentQueryOptions, token: CancellationToken): Promise<Dto<IExternalCustomAgentResource>[] | undefined>;
$provideInstructions(handle: number, options: IInstructionQueryOptions, token: CancellationToken): Promise<Dto<IExternalCustomAgentResource>[] | undefined>;
$setRequestTools(requestId: string, tools: UserSelectedTools): void;
}
export interface IChatParticipantMetadata {
Expand Down
37 changes: 35 additions & 2 deletions src/vs/workbench/api/common/extHostChatAgents2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { ExtHostLanguageModels } from './extHostLanguageModels.js';
import { ExtHostLanguageModelTools } from './extHostLanguageModelTools.js';
import * as typeConvert from './extHostTypeConverters.js';
import * as extHostTypes from './extHostTypes.js';
import { ICustomAgentQueryOptions, IExternalCustomAgent } from '../../contrib/chat/common/promptSyntax/service/promptsService.js';
import { ICustomAgentQueryOptions, IExternalCustomAgentResource, IInstructionQueryOptions } from '../../contrib/chat/common/promptSyntax/service/promptsService.js';
import { ExtHostDocumentsAndEditors } from './extHostDocumentsAndEditors.js';

export class ChatAgentResponseStream {
Expand Down Expand Up @@ -400,6 +400,8 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS

private static _customAgentsProviderIdPool = 0;
private readonly _customAgentsProviders = new Map<number, { extension: IExtensionDescription; provider: vscode.CustomAgentsProvider }>();
private static _instructionsProviderIdPool = 0;
private readonly _instructionsProviders = new Map<number, { extension: IExtensionDescription; provider: vscode.InstructionsProvider }>();

private readonly _sessionDisposables: DisposableResourceMap<DisposableStore> = this._register(new DisposableResourceMap());
private readonly _completionDisposables: DisposableMap<number, DisposableStore> = this._register(new DisposableMap());
Expand Down Expand Up @@ -501,6 +503,28 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS
return disposables;
}

registerInstructionsProvider(extension: IExtensionDescription, provider: vscode.InstructionsProvider): vscode.Disposable {
const handle = ExtHostChatAgents2._instructionsProviderIdPool++;
this._instructionsProviders.set(handle, { extension, provider });
this._proxy.$registerInstructionsProvider(handle, extension.identifier);

const disposables = new DisposableStore();

// Listen to provider change events and notify main thread
if (provider.onDidChangeInstructions) {
disposables.add(provider.onDidChangeInstructions(() => {
this._proxy.$onDidChangeInstructions(handle);
}));
}

disposables.add(toDisposable(() => {
this._instructionsProviders.delete(handle);
this._proxy.$unregisterInstructionsProvider(handle);
}));

return disposables;
}

async $provideRelatedFiles(handle: number, request: IChatRequestDraft, token: CancellationToken): Promise<Dto<IChatRelatedFile>[] | undefined> {
const provider = this._relatedFilesProviders.get(handle);
if (!provider) {
Expand All @@ -511,7 +535,7 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS
return await provider.provider.provideRelatedFiles(extRequestDraft, token) ?? undefined;
}

async $provideCustomAgents(handle: number, options: ICustomAgentQueryOptions, token: CancellationToken): Promise<IExternalCustomAgent[] | undefined> {
async $provideCustomAgents(handle: number, options: ICustomAgentQueryOptions, token: CancellationToken): Promise<IExternalCustomAgentResource[] | undefined> {
const providerData = this._customAgentsProviders.get(handle);
if (!providerData) {
return Promise.resolve(undefined);
Comment thread
pwang347 marked this conversation as resolved.
Outdated
Expand All @@ -520,6 +544,15 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS
return await providerData.provider.provideCustomAgents(options, token) ?? undefined;
}

async $provideInstructions(handle: number, options: IInstructionQueryOptions, token: CancellationToken): Promise<IExternalCustomAgentResource[] | undefined> {
const providerData = this._instructionsProviders.get(handle);
if (!providerData) {
return Promise.resolve(undefined);
}

return await providerData.provider.provideInstructions(options, token) ?? undefined;
}

async $detectChatParticipant(handle: number, requestDto: Dto<IChatAgentRequest>, context: { history: IChatAgentHistoryEntryDto[] }, options: { location: ChatAgentLocation; participants?: vscode.ChatParticipantMetadata[] }, token: CancellationToken): Promise<vscode.ChatParticipantDetectionResult | null | undefined> {
const detector = this._participantDetectionProviders.get(handle);
if (!detector) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { PromptsConfig } from './config/config.js';
import { isPromptOrInstructionsFile } from './config/promptFileLocations.js';
import { PromptsType } from './promptTypes.js';
import { ParsedPromptFile } from './promptFileParser.js';
import { IPromptPath, IPromptsService } from './service/promptsService.js';
import { IPromptPath, IPromptsService, PromptsStorage, ExtensionAgentSourceType } from './service/promptsService.js';
import { OffsetRange } from '../../../../../editor/common/core/ranges/offsetRange.js';
import { ChatConfiguration } from '../constants.js';

Expand Down Expand Up @@ -88,6 +88,9 @@ export class ComputeAutomaticInstructions {
const telemetryEvent: InstructionsCollectionEvent = newInstructionsCollectionEvent();
const context = this._getContext(variables);

// add instructions from providers (always included)
await this.addProviderInstructions(instructionFiles, context, variables, telemetryEvent, token);
Comment thread
pwang347 marked this conversation as resolved.
Outdated

// find instructions where the `applyTo` matches the attached context
await this.addApplyingInstructions(instructionFiles, context, variables, telemetryEvent, token);

Expand All @@ -112,6 +115,42 @@ export class ComputeAutomaticInstructions {
this._telemetryService.publicLog2<InstructionsCollectionEvent, InstructionsCollectionClassification>('instructionsCollected', telemetryEvent);
}

/** public for testing */
public async addProviderInstructions(instructionFiles: readonly IPromptPath[], context: { files: ResourceSet; instructions: ResourceSet }, variables: ChatRequestVariableSet, telemetryEvent: InstructionsCollectionEvent, token: CancellationToken): Promise<void> {

for (const instructionPath of instructionFiles) {
// Only include instructions from extension providers (not contributed files)
if (instructionPath.storage !== PromptsStorage.extension) {
continue;
}

// Check that the source is from a provider, not a contribution
if (instructionPath.source !== ExtensionAgentSourceType.provider) {
continue;
}

const uri = instructionPath.uri;

if (context.instructions.has(uri)) {
// the instruction file is already part of the input or has already been processed
this._logService.trace(`[InstructionsContextComputer] Skipping already processed provider instruction file: ${uri}`);
continue;
}

const parsedFile = await this._parseInstructionsFile(uri, token);
if (!parsedFile) {
this._logService.trace(`[InstructionsContextComputer] Unable to read provider instruction: ${uri}`);
continue;
}

this._logService.trace(`[InstructionsContextComputer] Adding provider instruction: ${uri}`);

const reason = localize('instruction.file.reason.provider', 'Automatically attached from extension provider');
variables.add(toPromptFileVariableEntry(uri, PromptFileVariableKind.Instruction, reason, true));
telemetryEvent.applyingInstructionsCount++;
}
}

/** public for testing */
public async addApplyingInstructions(instructionFiles: readonly IPromptPath[], context: { files: ResourceSet; instructions: ResourceSet }, variables: ChatRequestVariableSet, telemetryEvent: InstructionsCollectionEvent, token: CancellationToken): Promise<void> {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,25 @@ import { ResourceSet } from '../../../../../../base/common/map.js';
*/
export const CUSTOM_AGENTS_PROVIDER_ACTIVATION_EVENT = 'onCustomAgentsProvider';

/**
* Activation event for instructions providers.
*/
export const INSTRUCTIONS_PROVIDER_ACTIVATION_EVENT = 'onInstructionsProvider';

/**
* Options for querying custom agents.
*/
export interface ICustomAgentQueryOptions { }

/**
* Options for querying instructions.
*/
export interface IInstructionQueryOptions { }

/**
* Represents a custom agent resource from an external provider.
*/
export interface IExternalCustomAgent {
export interface IExternalCustomAgentResource {
/**
* The unique identifier/name of the custom agent resource.
*/
Expand Down Expand Up @@ -324,7 +334,19 @@ export interface IPromptsService extends IDisposable {
*/
registerCustomAgentsProvider(extension: IExtensionDescription, provider: {
onDidChangeCustomAgents?: Event<void>;
provideCustomAgents: (options: ICustomAgentQueryOptions, token: CancellationToken) => Promise<IExternalCustomAgent[] | undefined>;
provideCustomAgents: (options: ICustomAgentQueryOptions, token: CancellationToken) => Promise<IExternalCustomAgentResource[] | undefined>;
}): IDisposable;

/**
* Registers an InstructionsProvider that can provide instructions for repositories.
* This is part of the proposed API and requires the chatParticipantPrivate proposal.
* @param extension The extension registering the provider.
* @param provider The provider implementation with optional change event.
* @returns A disposable that unregisters the provider when disposed.
*/
registerInstructionsProvider(extension: IExtensionDescription, provider: {
onDidChangeInstructions?: Event<void>;
provideInstructions: (options: IInstructionQueryOptions, token: CancellationToken) => Promise<IExternalCustomAgentResource[] | undefined>;
}): IDisposable;

/**
Expand Down
Loading
Loading