Skip to content
Merged
Show file tree
Hide file tree
Changes from 30 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
4 changes: 4 additions & 0 deletions src/vs/platform/extensions/common/extensionsApiProposals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ const _allApiProposals = {
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts',
version: 11
},
chatPromptFiles: {
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatPromptFiles.d.ts',
version: 1
},
chatProvider: {
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatProvider.d.ts',
version: 4
Expand Down
46 changes: 26 additions & 20 deletions src/vs/workbench/api/browser/mainThreadChatAgents2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ import { IUriIdentityService } from '../../../platform/uriIdentity/common/uriIde
import { IChatWidgetService } from '../../contrib/chat/browser/chat.js';
import { AddDynamicVariableAction, IAddDynamicVariableContext } from '../../contrib/chat/browser/attachments/chatDynamicVariables.js';
import { IChatAgentHistoryEntry, IChatAgentImplementation, IChatAgentRequest, IChatAgentService } from '../../contrib/chat/common/participants/chatAgents.js';
import { ICustomAgentQueryOptions, IPromptsService } from '../../contrib/chat/common/promptSyntax/service/promptsService.js';
import { IPromptFileContext, IPromptsService } from '../../contrib/chat/common/promptSyntax/service/promptsService.js';
import { isValidPromptType } from '../../contrib/chat/common/promptSyntax/promptTypes.js';
import { IChatEditingService, IChatRelatedFileProviderMetadata } from '../../contrib/chat/common/editing/chatEditingService.js';
import { IChatModel } from '../../contrib/chat/common/model/chatModel.js';
import { ChatRequestAgentPart } from '../../contrib/chat/common/requestParser/chatParserTypes.js';
Expand Down Expand Up @@ -96,8 +97,8 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA

private readonly _chatRelatedFilesProviders = this._register(new DisposableMap<number, IDisposable>());

private readonly _customAgentsProviders = this._register(new DisposableMap<number, IDisposable>());
private readonly _customAgentsProviderEmitters = this._register(new DisposableMap<number, Emitter<void>>());
private readonly _promptFileProviders = this._register(new DisposableMap<number, IDisposable>());
private readonly _promptFileProviderEmitters = 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 @@ -435,41 +436,46 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA
this._chatRelatedFilesProviders.deleteAndDispose(handle);
}

async $registerCustomAgentsProvider(handle: number, extensionId: ExtensionIdentifier): Promise<void> {
async $registerPromptFileProvider(handle: number, type: string, extensionId: ExtensionIdentifier): Promise<void> {
const extension = await this._extensionService.getExtension(extensionId.value);
if (!extension) {
this._logService.error(`[MainThreadChatAgents2] Could not find extension for CustomAgentsProvider: ${extensionId.value}`);
this._logService.error(`[MainThreadChatAgents2] Could not find extension for prompt file provider: ${extensionId.value}`);
return;
}

if (!isValidPromptType(type)) {
this._logService.error(`[MainThreadChatAgents2] Invalid contribution type: ${type}`);
return;
}

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

const disposable = this._promptsService.registerCustomAgentsProvider(extension, {
onDidChangeCustomAgents: emitter.event,
provideCustomAgents: async (options: ICustomAgentQueryOptions, token: CancellationToken) => {
const agents = await this._proxy.$provideCustomAgents(handle, options, token);
if (!agents) {
const disposable = this._promptsService.registerPromptFileProvider(extension, type, {
onDidChangePromptFiles: emitter.event,
providePromptFiles: async (context: IPromptFileContext, token: CancellationToken) => {
const contributions = await this._proxy.$providePromptFiles(handle, context, token);
if (!contributions) {
return undefined;
}
// Convert UriComponents to URI
return agents.map(agent => ({
...agent,
uri: URI.revive(agent.uri)
return contributions.map(c => ({
...c,
uri: URI.revive(c.uri)
}));
}
});

this._customAgentsProviders.set(handle, disposable);
this._promptFileProviders.set(handle, disposable);
}

$unregisterCustomAgentsProvider(handle: number): void {
this._customAgentsProviders.deleteAndDispose(handle);
this._customAgentsProviderEmitters.deleteAndDispose(handle);
$unregisterPromptFileProvider(handle: number): void {
this._promptFileProviders.deleteAndDispose(handle);
this._promptFileProviderEmitters.deleteAndDispose(handle);
}

$onDidChangeCustomAgents(handle: number): void {
const emitter = this._customAgentsProviderEmitters.get(handle);
$onDidChangePromptFiles(handle: number): void {
const emitter = this._promptFileProviderEmitters.get(handle);
if (emitter) {
emitter.fire();
}
Expand Down
15 changes: 12 additions & 3 deletions src/vs/workbench/api/common/extHost.api.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { getRemoteName } from '../../../platform/remote/common/remoteHosts.js';
import { TelemetryTrustedValue } from '../../../platform/telemetry/common/telemetryUtils.js';
import { EditSessionIdentityMatch } from '../../../platform/workspace/common/editSessions.js';
import { DebugConfigurationProviderTriggerKind } from '../../contrib/debug/common/debug.js';
import { PromptsType } from '../../contrib/chat/common/promptSyntax/promptTypes.js';
import { ExtensionDescriptionRegistry } from '../../services/extensions/common/extensionDescriptionRegistry.js';
import { UIKind } from '../../services/extensions/common/extensionHostProtocol.js';
import { checkProposedApiEnabled, isProposedApiEnabled } from '../../services/extensions/common/extensions.js';
Expand Down Expand Up @@ -1541,9 +1542,17 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
checkProposedApiEnabled(extension, 'chatContextProvider');
return extHostChatContext.registerChatContextProvider(selector ? checkSelector(selector) : undefined, `${extension.id}-${id}`, provider);
},
registerCustomAgentsProvider(provider: vscode.CustomAgentsProvider): vscode.Disposable {
checkProposedApiEnabled(extension, 'chatParticipantPrivate');
return extHostChatAgents2.registerCustomAgentsProvider(extension, provider);
registerCustomAgentProvider(provider: vscode.CustomAgentProvider): vscode.Disposable {
checkProposedApiEnabled(extension, 'chatPromptFiles');
return extHostChatAgents2.registerPromptFileProvider(extension, PromptsType.agent, provider);
},
registerInstructionsProvider(provider: vscode.InstructionsProvider): vscode.Disposable {
checkProposedApiEnabled(extension, 'chatPromptFiles');
return extHostChatAgents2.registerPromptFileProvider(extension, PromptsType.instructions, provider);
},
registerPromptFileProvider(provider: vscode.PromptFileProvider): vscode.Disposable {
checkProposedApiEnabled(extension, 'chatPromptFiles');
return extHostChatAgents2.registerPromptFileProvider(extension, PromptsType.prompt, provider);
},
};

Expand Down
10 changes: 5 additions & 5 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/attachments
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/tools/languageModelToolsService.js';
import { ICustomAgentQueryOptions, IExternalCustomAgent } from '../../contrib/chat/common/promptSyntax/service/promptsService.js';
import { IPromptFileContext, IPromptFileResource } 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 @@ -1394,9 +1394,9 @@ export interface MainThreadChatAgentsShape2 extends IChatAgentProgressShape, IDi
$unregisterChatParticipantDetectionProvider(handle: number): void;
$registerRelatedFilesProvider(handle: number, metadata: IChatRelatedFilesProviderMetadata): void;
$unregisterRelatedFilesProvider(handle: number): void;
$registerCustomAgentsProvider(handle: number, extension: ExtensionIdentifier): void;
$unregisterCustomAgentsProvider(handle: number): void;
$onDidChangeCustomAgents(handle: number): void;
$registerPromptFileProvider(handle: number, type: string, extension: ExtensionIdentifier): void;
$unregisterPromptFileProvider(handle: number): void;
$onDidChangePromptFiles(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 @@ -1462,7 +1462,7 @@ 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>;
$providePromptFiles(handle: number, context: IPromptFileContext, token: CancellationToken): Promise<Dto<IPromptFileResource>[] | undefined>;
$setRequestTools(requestId: string, tools: UserSelectedTools): void;
}
export interface IChatParticipantMetadata {
Expand Down
56 changes: 40 additions & 16 deletions src/vs/workbench/api/common/extHostChatAgents2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ 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 { IPromptFileContext, IPromptFileResource } from '../../contrib/chat/common/promptSyntax/service/promptsService.js';
import { PromptsType } from '../../contrib/chat/common/promptSyntax/promptTypes.js';
import { ExtHostDocumentsAndEditors } from './extHostDocumentsAndEditors.js';

export class ChatAgentResponseStream {
Expand Down Expand Up @@ -398,8 +399,8 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS
private static _relatedFilesProviderIdPool = 0;
private readonly _relatedFilesProviders = new Map<number, ExtHostRelatedFilesProvider>();

private static _customAgentsProviderIdPool = 0;
private readonly _customAgentsProviders = new Map<number, { extension: IExtensionDescription; provider: vscode.CustomAgentsProvider }>();
private static _contributionsProviderIdPool = 0;
private readonly _promptFileProviders = new Map<number, { extension: IExtensionDescription; provider: vscode.CustomAgentProvider | vscode.InstructionsProvider | vscode.PromptFileProvider }>();

private readonly _sessionDisposables: DisposableResourceMap<DisposableStore> = this._register(new DisposableResourceMap());
private readonly _completionDisposables: DisposableMap<number, DisposableStore> = this._register(new DisposableMap());
Expand Down Expand Up @@ -479,23 +480,37 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS
});
}

registerCustomAgentsProvider(extension: IExtensionDescription, provider: vscode.CustomAgentsProvider): vscode.Disposable {
const handle = ExtHostChatAgents2._customAgentsProviderIdPool++;
this._customAgentsProviders.set(handle, { extension, provider });
this._proxy.$registerCustomAgentsProvider(handle, extension.identifier);
/**
* Internal method that handles all prompt file provider types.
* Routes custom agents, instructions, and prompt files to the unified internal implementation.
*/
registerPromptFileProvider(extension: IExtensionDescription, type: PromptsType, provider: vscode.CustomAgentProvider | vscode.InstructionsProvider | vscode.PromptFileProvider): vscode.Disposable {
const handle = ExtHostChatAgents2._contributionsProviderIdPool++;
this._promptFileProviders.set(handle, { extension, provider });
this._proxy.$registerPromptFileProvider(handle, type, extension.identifier);

const disposables = new DisposableStore();

// Listen to provider change events and notify main thread
if (provider.onDidChangeCustomAgents) {
disposables.add(provider.onDidChangeCustomAgents(() => {
this._proxy.$onDidChangeCustomAgents(handle);
// Check for the appropriate event based on the provider type
let changeEvent: vscode.Event<void> | undefined;
if ('onDidChangeCustomAgents' in provider) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use a switch over 'type' to know the name of the event. A instruction provider could also have a field 'onDidChangeCustomAgents'. That would be ok to do

changeEvent = provider.onDidChangeCustomAgents;
} else if ('onDidChangeInstructions' in provider) {
changeEvent = provider.onDidChangeInstructions;
} else if ('onDidChangePromptFiles' in provider) {
changeEvent = provider.onDidChangePromptFiles;
}

if (changeEvent) {
disposables.add(changeEvent(() => {
this._proxy.$onDidChangePromptFiles(handle);
}));
}

disposables.add(toDisposable(() => {
this._customAgentsProviders.delete(handle);
this._proxy.$unregisterCustomAgentsProvider(handle);
this._promptFileProviders.delete(handle);
this._proxy.$unregisterPromptFileProvider(handle);
}));

return disposables;
Expand All @@ -511,13 +526,22 @@ 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> {
const providerData = this._customAgentsProviders.get(handle);
async $providePromptFiles(handle: number, context: IPromptFileContext, token: CancellationToken): Promise<IPromptFileResource[] | undefined> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about this more, we should pass type: PromptsType to $providePromptFiles and use it to know whether to use 'provideCustomAgents', 'provideInstructions' or 'providePromptFiles'. A client could implement a provider that has all these methods that would be completly ok to do.

const providerData = this._promptFileProviders.get(handle);
if (!providerData) {
return Promise.resolve(undefined);
return undefined;
}

return await providerData.provider.provideCustomAgents(options, token) ?? undefined;
const provider = providerData.provider;
// Call the appropriate method based on the provider type
if ('provideCustomAgents' in provider) {
return await provider.provideCustomAgents(context, token) ?? undefined;
} else if ('provideInstructions' in provider) {
return await provider.provideInstructions(context, token) ?? undefined;
} else if ('providePromptFiles' in provider) {
return await provider.providePromptFiles(context, token) ?? undefined;
}
return undefined;
}

async $detectChatParticipant(handle: number, requestDto: Dto<IChatAgentRequest>, context: { history: IChatAgentHistoryEntryDto[] }, options: { location: ChatAgentLocation; participants?: vscode.ChatParticipantMetadata[] }, token: CancellationToken): Promise<vscode.ChatParticipantDetectionResult | null | undefined> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,21 @@ import { IHandOff, ParsedPromptFile } from '../promptFileParser.js';
import { ResourceSet } from '../../../../../../base/common/map.js';

/**
* Activation event for custom agent providers.
* Activation events for prompt file providers.
*/
export const CUSTOM_AGENTS_PROVIDER_ACTIVATION_EVENT = 'onCustomAgentsProvider';
export const CUSTOM_AGENT_PROVIDER_ACTIVATION_EVENT = 'onCustomAgentProvider';
export const INSTRUCTIONS_PROVIDER_ACTIVATION_EVENT = 'onInstructionsProvider';
export const PROMPT_FILE_PROVIDER_ACTIVATION_EVENT = 'onPromptFileProvider';

/**
* Options for querying custom agents.
* Context for querying prompt files.
*/
export interface ICustomAgentQueryOptions { }
export interface IPromptFileContext { }

/**
* Represents a custom agent resource from an external provider.
* Represents a prompt file resource from an external provider.
*/
export interface IExternalCustomAgent {
/**
* The unique identifier/name of the custom agent resource.
*/
readonly name: string;

/**
* A description of what the custom agent resource does.
*/
readonly description: string;

export interface IPromptFileResource {
/**
* The URI to the agent or prompt resource file.
*/
Expand Down Expand Up @@ -316,15 +308,15 @@ export interface IPromptsService extends IDisposable {
setDisabledPromptFiles(type: PromptsType, uris: ResourceSet): void;

/**
* Registers a CustomAgentsProvider that can provide custom agents for repositories.
* This is part of the proposed API and requires the chatParticipantPrivate proposal.
* Registers a prompt file provider that can provide prompt files for repositories.
* @param extension The extension registering the provider.
* @param type The type of contribution.
* @param provider The provider implementation with optional change event.
* @returns A disposable that unregisters the provider when disposed.
*/
registerCustomAgentsProvider(extension: IExtensionDescription, provider: {
onDidChangeCustomAgents?: Event<void>;
provideCustomAgents: (options: ICustomAgentQueryOptions, token: CancellationToken) => Promise<IExternalCustomAgent[] | undefined>;
registerPromptFileProvider(extension: IExtensionDescription, type: PromptsType, provider: {
onDidChangePromptFiles?: Event<void>;
providePromptFiles: (context: IPromptFileContext, token: CancellationToken) => Promise<IPromptFileResource[] | undefined>;
}): IDisposable;

/**
Expand Down
Loading
Loading