mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-31 00:10:04 +08:00
Add support for custom chat agents in the API (#298227)
* Add support for custom chat agents in the API - Introduced `chatCustomAgents` proposal in extensions API. - Implemented methods to handle custom agents in `MainThreadChatAgents2`. - Added `ICustomAgentDto` interface and related functionality in extHost. - Created new type definitions for custom agents in `vscode.proposed.chatCustomAgents.d.ts`. * Filter custom agents by visibility before pushing to the proxy * Refactor onDidChangeCustomAgents to use direct event listener * Update custom agent tools property to allow undefined values * Add chatCustomAgents to enabledApiProposals in package.json * update * update * support skills * support instructions * update * update --------- Co-authored-by: Martin Aeschlimann <martinae@microsoft.com>
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
"authSession",
|
||||
"environmentPower",
|
||||
"chatParticipantPrivate",
|
||||
"chatPromptFiles",
|
||||
"chatProvider",
|
||||
"contribStatusBarItems",
|
||||
"contribViewsRemote",
|
||||
|
||||
@@ -40,7 +40,7 @@ import { ILanguageModelToolsService } from '../../contrib/chat/common/tools/lang
|
||||
import { IExtHostContext, extHostNamedCustomer } from '../../services/extensions/common/extHostCustomers.js';
|
||||
import { IExtensionService } from '../../services/extensions/common/extensions.js';
|
||||
import { Dto } from '../../services/extensions/common/proxyIdentifier.js';
|
||||
import { ExtHostChatAgentsShape2, ExtHostContext, IChatNotebookEditDto, IChatParticipantMetadata, IChatProgressDto, IChatSessionContextDto, IDynamicChatAgentProps, IExtensionChatAgentMetadata, MainContext, MainThreadChatAgentsShape2 } from '../common/extHost.protocol.js';
|
||||
import { ExtHostChatAgentsShape2, ExtHostContext, IChatNotebookEditDto, IChatParticipantMetadata, IChatProgressDto, IChatSessionContextDto, ICustomAgentDto, IDynamicChatAgentProps, IExtensionChatAgentMetadata, IInstructionDto, ISkillDto, MainContext, MainThreadChatAgentsShape2 } from '../common/extHost.protocol.js';
|
||||
import { NotebookDto } from './mainThreadNotebookDto.js';
|
||||
|
||||
interface AgentData {
|
||||
@@ -153,6 +153,24 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA
|
||||
|
||||
// Push the initial active session if there is already a focused widget
|
||||
this._acceptActiveChatSession(this._chatWidgetService.lastFocusedWidget);
|
||||
|
||||
// Push custom agents to ext host
|
||||
void this._pushCustomAgents();
|
||||
this._register(this._promptsService.onDidChangeCustomAgents(() => {
|
||||
void this._pushCustomAgents();
|
||||
}));
|
||||
|
||||
// Push instructions to ext host
|
||||
void this._pushInstructions();
|
||||
this._register(this._promptsService.onDidChangeInstructions(() => {
|
||||
void this._pushInstructions();
|
||||
}));
|
||||
|
||||
// Push skills to ext host
|
||||
void this._pushSkills();
|
||||
this._register(this._promptsService.onDidChangeSkills(() => {
|
||||
void this._pushSkills();
|
||||
}));
|
||||
}
|
||||
|
||||
private _acceptActiveChatSession(widget: IChatWidget | undefined): void {
|
||||
@@ -161,6 +179,36 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA
|
||||
this._proxy.$acceptActiveChatSession(isLocal ? sessionResource : undefined);
|
||||
}
|
||||
|
||||
private async _pushCustomAgents(): Promise<void> {
|
||||
try {
|
||||
const customAgents = await this._promptsService.getCustomAgents(CancellationToken.None);
|
||||
const dtos: ICustomAgentDto[] = customAgents.map(agent => ({ uri: agent.uri }));
|
||||
this._proxy.$acceptCustomAgents(dtos);
|
||||
} catch (error) {
|
||||
this._logService.error('[chat] Failed to push custom agents to extension host', error);
|
||||
}
|
||||
}
|
||||
|
||||
private async _pushInstructions(): Promise<void> {
|
||||
try {
|
||||
const instructions = await this._promptsService.getInstructionFiles(CancellationToken.None);
|
||||
const dtos: IInstructionDto[] = instructions.map(instruction => ({ uri: instruction.uri }));
|
||||
this._proxy.$acceptInstructions(dtos);
|
||||
} catch (error) {
|
||||
this._logService.error('[chat] Failed to push instructions to extension host', error);
|
||||
}
|
||||
}
|
||||
|
||||
private async _pushSkills(): Promise<void> {
|
||||
try {
|
||||
const skills = await this._promptsService.findAgentSkills(CancellationToken.None) ?? [];
|
||||
const dtos: ISkillDto[] = skills.map(skill => ({ uri: skill.uri }));
|
||||
this._proxy.$acceptSkills(dtos);
|
||||
} catch (error) {
|
||||
this._logService.error('[chat] Failed to push skills to extension host', error);
|
||||
}
|
||||
}
|
||||
|
||||
$unregisterAgent(handle: number): void {
|
||||
this._agents.deleteAndDispose(handle);
|
||||
}
|
||||
|
||||
@@ -1677,6 +1677,30 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
checkProposedApiEnabled(extension, 'chatDebug');
|
||||
return extHostChatDebug.registerChatDebugLogProvider(provider);
|
||||
},
|
||||
get customAgents() {
|
||||
checkProposedApiEnabled(extension, 'chatPromptFiles');
|
||||
return extHostChatAgents2.customAgents as readonly vscode.ChatResource[];
|
||||
},
|
||||
onDidChangeCustomAgents: (listener, thisArgs?, disposables?) => {
|
||||
checkProposedApiEnabled(extension, 'chatPromptFiles');
|
||||
return extHostChatAgents2.onDidChangeCustomAgents(listener, thisArgs, disposables);
|
||||
},
|
||||
get instructions() {
|
||||
checkProposedApiEnabled(extension, 'chatPromptFiles');
|
||||
return extHostChatAgents2.instructions as readonly vscode.ChatResource[];
|
||||
},
|
||||
onDidChangeInstructions: (listener, thisArgs?, disposables?) => {
|
||||
checkProposedApiEnabled(extension, 'chatPromptFiles');
|
||||
return extHostChatAgents2.onDidChangeInstructions(listener, thisArgs, disposables);
|
||||
},
|
||||
get skills() {
|
||||
checkProposedApiEnabled(extension, 'chatPromptFiles');
|
||||
return extHostChatAgents2.skills as readonly vscode.ChatResource[];
|
||||
},
|
||||
onDidChangeSkills: (listener, thisArgs?, disposables?) => {
|
||||
checkProposedApiEnabled(extension, 'chatPromptFiles');
|
||||
return extHostChatAgents2.onDidChangeSkills(listener, thisArgs, disposables);
|
||||
},
|
||||
};
|
||||
|
||||
// namespace: lm
|
||||
|
||||
@@ -1614,6 +1614,21 @@ export interface ExtHostChatAgentsShape2 {
|
||||
$setRequestTools(requestId: string, tools: UserSelectedTools): void;
|
||||
$setYieldRequested(requestId: string, value: boolean): void;
|
||||
$acceptActiveChatSession(sessionResource: UriComponents | undefined): void;
|
||||
$acceptCustomAgents(agents: ICustomAgentDto[]): void;
|
||||
$acceptInstructions(instructions: IInstructionDto[]): void;
|
||||
$acceptSkills(skills: ISkillDto[]): void;
|
||||
}
|
||||
|
||||
export interface ICustomAgentDto {
|
||||
uri: UriComponents;
|
||||
}
|
||||
|
||||
export interface IInstructionDto {
|
||||
uri: UriComponents;
|
||||
}
|
||||
|
||||
export interface ISkillDto {
|
||||
uri: UriComponents;
|
||||
}
|
||||
export interface IChatParticipantMetadata {
|
||||
participant: string;
|
||||
|
||||
@@ -27,7 +27,7 @@ import { LocalChatSessionUri } from '../../contrib/chat/common/model/chatUri.js'
|
||||
import { ChatAgentLocation } from '../../contrib/chat/common/constants.js';
|
||||
import { checkProposedApiEnabled, isProposedApiEnabled } from '../../services/extensions/common/extensions.js';
|
||||
import { Dto } from '../../services/extensions/common/proxyIdentifier.js';
|
||||
import { ExtHostChatAgentsShape2, IChatAgentCompletionItem, IChatAgentHistoryEntryDto, IChatAgentProgressShape, IChatProgressDto, IChatSessionContextDto, IExtensionChatAgentMetadata, IMainContext, MainContext, MainThreadChatAgentsShape2 } from './extHost.protocol.js';
|
||||
import { ExtHostChatAgentsShape2, IChatAgentCompletionItem, IChatAgentHistoryEntryDto, IChatAgentProgressShape, IChatProgressDto, IChatSessionContextDto, ICustomAgentDto, IExtensionChatAgentMetadata, IInstructionDto, IMainContext, ISkillDto, MainContext, MainThreadChatAgentsShape2 } from './extHost.protocol.js';
|
||||
import { CommandsConverter, ExtHostCommands } from './extHostCommands.js';
|
||||
import { ExtHostDiagnostics } from './extHostDiagnostics.js';
|
||||
import { ExtHostDocuments } from './extHostDocuments.js';
|
||||
@@ -487,6 +487,17 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS
|
||||
private readonly _onDidDisposeChatSession = this._register(new Emitter<string>());
|
||||
readonly onDidDisposeChatSession = this._onDidDisposeChatSession.event;
|
||||
|
||||
private readonly _onDidChangeCustomAgents = this._register(new Emitter<void>());
|
||||
readonly onDidChangeCustomAgents = this._onDidChangeCustomAgents.event;
|
||||
private readonly _onDidChangeInstructions = this._register(new Emitter<void>());
|
||||
readonly onDidChangeInstructions = this._onDidChangeInstructions.event;
|
||||
private readonly _onDidChangeSkills = this._register(new Emitter<void>());
|
||||
readonly onDidChangeSkills = this._onDidChangeSkills.event;
|
||||
|
||||
private _customAgents: vscode.ChatResource[] = [];
|
||||
private _instructions: vscode.ChatResource[] = [];
|
||||
private _skills: vscode.ChatResource[] = [];
|
||||
|
||||
private _activeChatPanelSessionResource: URI | undefined;
|
||||
|
||||
private readonly _onDidChangeActiveChatPanelSessionResource = this._register(new Emitter<URI | undefined>());
|
||||
@@ -496,6 +507,33 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS
|
||||
return this._activeChatPanelSessionResource;
|
||||
}
|
||||
|
||||
get customAgents(): readonly vscode.ChatResource[] {
|
||||
return this._customAgents;
|
||||
}
|
||||
|
||||
get instructions(): readonly vscode.ChatResource[] {
|
||||
return this._instructions;
|
||||
}
|
||||
|
||||
get skills(): readonly vscode.ChatResource[] {
|
||||
return this._skills;
|
||||
}
|
||||
|
||||
$acceptCustomAgents(agents: ICustomAgentDto[]): void {
|
||||
this._customAgents = agents.map(a => Object.freeze({ uri: URI.revive(a.uri) }));
|
||||
this._onDidChangeCustomAgents.fire();
|
||||
}
|
||||
|
||||
$acceptInstructions(instructions: IInstructionDto[]): void {
|
||||
this._instructions = instructions.map(i => Object.freeze({ uri: URI.revive(i.uri) }));
|
||||
this._onDidChangeInstructions.fire();
|
||||
}
|
||||
|
||||
$acceptSkills(skills: ISkillDto[]): void {
|
||||
this._skills = skills.map(s => Object.freeze({ uri: URI.revive(s.uri) }));
|
||||
this._onDidChangeSkills.fire();
|
||||
}
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
private readonly _logService: ILogService,
|
||||
|
||||
@@ -417,6 +417,11 @@ export interface IPromptsService extends IDisposable {
|
||||
*/
|
||||
readonly onDidChangeCustomAgents: Event<void>;
|
||||
|
||||
/**
|
||||
* Event that is triggered when the list of instruction files changes.
|
||||
*/
|
||||
readonly onDidChangeInstructions: Event<void>;
|
||||
|
||||
/**
|
||||
* Finds all available custom agents
|
||||
* @param sessionResource Optional session resource to scope debug logging to a specific session.
|
||||
@@ -483,6 +488,11 @@ export interface IPromptsService extends IDisposable {
|
||||
*/
|
||||
findAgentSkills(token: CancellationToken, sessionResource?: URI): Promise<IAgentSkill[] | undefined>;
|
||||
|
||||
/**
|
||||
* Event that is triggered when the list of skills changes.
|
||||
*/
|
||||
readonly onDidChangeSkills: Event<void>;
|
||||
|
||||
/**
|
||||
* Gets detailed discovery information for a prompt type.
|
||||
* This includes all files found and their load/skip status with reasons.
|
||||
|
||||
@@ -152,6 +152,7 @@ export class PromptsService extends Disposable implements IPromptsService {
|
||||
private readonly _contributedWhenKeys = new Set<string>();
|
||||
private readonly _contributedWhenClauses = new Map<string, string>();
|
||||
private readonly _onDidContributedWhenChange = this._register(new Emitter<void>());
|
||||
private readonly _onDidChangeInstructions = this._register(new Emitter<void>());
|
||||
private readonly _onDidPluginPromptFilesChange = this._register(new Emitter<void>());
|
||||
private readonly _onDidPluginHooksChange = this._register(new Emitter<void>());
|
||||
private _pluginPromptFilesByType = new Map<PromptsType, readonly IPluginPromptPath[]>();
|
||||
@@ -417,6 +418,7 @@ export class PromptsService extends Disposable implements IPromptsService {
|
||||
this.cachedCustomAgents.refresh();
|
||||
} else if (type === PromptsType.instructions) {
|
||||
this.cachedFileLocations[PromptsType.instructions] = undefined;
|
||||
this._onDidChangeInstructions.fire();
|
||||
} else if (type === PromptsType.prompt) {
|
||||
this.cachedFileLocations[PromptsType.prompt] = undefined;
|
||||
this.cachedSlashCommands.refresh();
|
||||
@@ -644,6 +646,14 @@ export class PromptsService extends Disposable implements IPromptsService {
|
||||
return this.cachedCustomAgents.onDidChange;
|
||||
}
|
||||
|
||||
public get onDidChangeInstructions(): Event<void> {
|
||||
return Event.any(
|
||||
this.getFileLocatorEvent(PromptsType.instructions),
|
||||
this._onDidContributedWhenChange.event,
|
||||
this._onDidChangeInstructions.event,
|
||||
);
|
||||
}
|
||||
|
||||
public async getCustomAgents(token: CancellationToken, sessionResource?: URI): Promise<readonly ICustomAgent[]> {
|
||||
const sw = StopWatch.create();
|
||||
const result = await this.cachedCustomAgents.get(token);
|
||||
|
||||
@@ -18,8 +18,8 @@ export class MockPromptsService implements IPromptsService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
private readonly _onDidChangeCustomChatModes = new Emitter<void>();
|
||||
readonly onDidChangeCustomAgents = this._onDidChangeCustomChatModes.event;
|
||||
private readonly _onDidChangeCustomAgents = new Emitter<void>();
|
||||
readonly onDidChangeCustomAgents = this._onDidChangeCustomAgents.event;
|
||||
|
||||
private readonly _onDidLogDiscovery = new Emitter<IPromptDiscoveryLogEntry>();
|
||||
readonly onDidLogDiscovery: Event<IPromptDiscoveryLogEntry> = this._onDidLogDiscovery.event;
|
||||
@@ -28,7 +28,7 @@ export class MockPromptsService implements IPromptsService {
|
||||
|
||||
setCustomModes(modes: ICustomAgent[]): void {
|
||||
this._customModes = modes;
|
||||
this._onDidChangeCustomChatModes.fire();
|
||||
this._onDidChangeCustomAgents.fire();
|
||||
}
|
||||
|
||||
async getCustomAgents(token: CancellationToken, sessionResource?: URI): Promise<readonly ICustomAgent[]> {
|
||||
@@ -72,4 +72,7 @@ export class MockPromptsService implements IPromptsService {
|
||||
getHooks(_token: CancellationToken, _sessionResource?: URI): Promise<any> { throw new Error('Method not implemented.'); }
|
||||
getInstructionFiles(_token: CancellationToken, _sessionResource?: URI): Promise<readonly IPromptPath[]> { throw new Error('Method not implemented.'); }
|
||||
dispose(): void { }
|
||||
onDidChangeInstructions: Event<void> = Event.None;
|
||||
onDidChangePromptFiles: Event<void> = Event.None;
|
||||
onDidChangeSkills: Event<void> = Event.None;
|
||||
}
|
||||
|
||||
@@ -103,6 +103,39 @@ declare module 'vscode' {
|
||||
// #region Chat Provider Registration
|
||||
|
||||
export namespace chat {
|
||||
/**
|
||||
* An event that fires when the list of {@link customAgents custom agents} changes.
|
||||
*/
|
||||
export const onDidChangeCustomAgents: Event<void>;
|
||||
|
||||
/**
|
||||
* The list of currently available custom agents. These are `.agent.md` files
|
||||
* from all sources (workspace, user, and extension-provided).
|
||||
*/
|
||||
export const customAgents: readonly ChatResource[];
|
||||
|
||||
/**
|
||||
* An event that fires when the list of {@link instructions instructions} changes.
|
||||
*/
|
||||
export const onDidChangeInstructions: Event<void>;
|
||||
|
||||
/**
|
||||
* The list of currently available instructions. These are `.instructions.md` files
|
||||
* from all sources (workspace, user, and extension-provided).
|
||||
*/
|
||||
export const instructions: readonly ChatResource[];
|
||||
|
||||
/**
|
||||
* An event that fires when the list of {@link skills skills} changes.
|
||||
*/
|
||||
export const onDidChangeSkills: Event<void>;
|
||||
|
||||
/**
|
||||
* The list of currently available skills. These are `SKILL.md` files
|
||||
* from all sources (workspace, user, and extension-provided).
|
||||
*/
|
||||
export const skills: readonly ChatResource[];
|
||||
|
||||
/**
|
||||
* Register a provider for custom agents.
|
||||
* @param provider The custom agent provider.
|
||||
|
||||
Reference in New Issue
Block a user