Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
a0dc282
Add notes on chat agent API
roblourens Sep 29, 2023
10d1639
Add request ID to context
roblourens Sep 29, 2023
0901cc7
variables
roblourens Sep 29, 2023
f259efe
Add partial implementation for another option for a chat agent API
roblourens Oct 2, 2023
e814a7a
update
roblourens Oct 3, 2023
3d5c07a
Notes from api sync
roblourens Oct 3, 2023
df0bebe
Merge branch 'main' into roblou/chatAgentAPI2
roblourens Oct 5, 2023
466c249
More notes
roblourens Oct 6, 2023
5e34001
Merge remote-tracking branch 'origin/main' into roblou/chatAgentAPI2
roblourens Oct 9, 2023
d880809
Can invoke an agent and get the response
roblourens Oct 9, 2023
0e2aa02
Provide a real request
roblourens Oct 9, 2023
bc1f128
Notes
roblourens Oct 9, 2023
490fe5a
add `slashCommandProvider` - not yet hooked up
jrieken Oct 10, 2023
6fc109a
add metadata properties inline, some comments
jrieken Oct 10, 2023
ac664b4
some more notes
jrieken Oct 10, 2023
1012d9b
Put the new API side-by-side with the old one
roblourens Oct 10, 2023
8fac6a2
Fix agent title in response
roblourens Oct 11, 2023
a9b2ec1
Fix agent display
roblourens Oct 11, 2023
c2d07dc
Send slashCommand to request
roblourens Oct 11, 2023
57cf3dd
Hook up variables
roblourens Oct 11, 2023
375df18
Get rid of package.json registration option
roblourens Oct 11, 2023
24d404a
Start to implement followups provider
roblourens Oct 11, 2023
3e1cbf4
Add comment
roblourens Oct 11, 2023
48dc935
Merge branch 'main' into roblou/chatAgentAPI2
jrieken Oct 11, 2023
e2767ae
make it `slashCommandProvider` all the way, use updateAgent for updat…
jrieken Oct 11, 2023
21d5122
update docs
jrieken Oct 11, 2023
aa18d5d
only ask for slash command completions when completing a slash-word
jrieken Oct 11, 2023
e6e87c1
use complex completion item label for command/agent completions
jrieken Oct 11, 2023
6e303cd
add `promptText` to `IParsedChatRequestPart` so that some parts don't…
jrieken Oct 11, 2023
54a1d3e
only allow agent and slash command at the beginning of the prompt
jrieken Oct 11, 2023
a4550ac
remove unused method
jrieken Oct 11, 2023
5a5a23d
some jsdoc, many renames so that stuff starts with `ChatAgent...`
jrieken Oct 11, 2023
153f3e9
reduce `createChatAgent` to the minimum, let the rest be set via setters
jrieken Oct 11, 2023
63cc813
in the renderer know if an agent has slash command and follow ups, sa…
jrieken Oct 11, 2023
2255a20
use `iconPath` to align with other APIs
jrieken Oct 11, 2023
570cf8f
more jsdoc and more obvious TODOs
jrieken Oct 11, 2023
20747b4
fix chat parser with "late" command
jrieken Oct 11, 2023
63c3163
handle error so that the request stops. where is the rendering tho?
jrieken Oct 11, 2023
4e26924
Show error message in response properly
roblourens Oct 11, 2023
c234970
Don't blow up global / list
roblourens Oct 11, 2023
ca538d8
Change proposal name
roblourens Oct 11, 2023
1147c5b
Inline followup types
roblourens Oct 11, 2023
84389f1
fix type
roblourens Oct 11, 2023
15fade4
Remove brace in error msg
roblourens Oct 12, 2023
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: 2 additions & 2 deletions build/lib/compilation.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion build/lib/compilation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ function generateApiProposalNames() {
eol = os.EOL;
}

const pattern = /vscode\.proposed\.([a-zA-Z]+)\.d\.ts$/;
const pattern = /vscode\.proposed\.([a-zA-Z\d]+)\.d\.ts$/;
const proposalNames = new Set<string>();

const input = es.through();
Expand Down
1 change: 1 addition & 0 deletions src/vs/workbench/api/browser/extensionHost.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import './mainThreadLocalization';
import './mainThreadBulkEdits';
import './mainThreadChatProvider';
import './mainThreadChatAgents';
import './mainThreadChatAgents2';
import './mainThreadChatVariables';
import './mainThreadCodeInsets';
import './mainThreadCLICommands';
Expand Down
49 changes: 29 additions & 20 deletions src/vs/workbench/api/browser/mainThreadChatAgents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { DisposableMap } from 'vs/base/common/lifecycle';
import { revive } from 'vs/base/common/marshalling';
import { IProgress } from 'vs/platform/progress/common/progress';
import { ExtHostChatAgentsShape, ExtHostContext, MainContext, MainThreadChatAgentsShape } from 'vs/workbench/api/common/extHost.protocol';
import { IChatAgentMetadata, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { IChatAgentCommand, IChatAgentMetadata, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { IChatProgress } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatSlashFragment } from 'vs/workbench/contrib/chat/common/chatSlashCommands';
import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers';

Expand All @@ -16,7 +17,7 @@ import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/ext
export class MainThreadChatAgents implements MainThreadChatAgentsShape {

private readonly _agents = new DisposableMap<number>;
private readonly _pendingProgress = new Map<number, IProgress<IChatSlashFragment>>();
private readonly _pendingProgress = new Map<number, IProgress<IChatProgress>>();
private readonly _proxy: ExtHostChatAgentsShape;

constructor(
Expand All @@ -34,29 +35,37 @@ export class MainThreadChatAgents implements MainThreadChatAgentsShape {
this._agents.clearAndDisposeAll();
}

$registerAgent(handle: number, name: string, metadata: IChatAgentMetadata): void {
if (!this._chatAgentService.hasAgent(name)) {
// dynamic!
this._chatAgentService.registerAgentData({
id: name,
metadata: revive(metadata)
});
}

const d = this._chatAgentService.registerAgentCallback(name, async (prompt, progress, history, token) => {
const requestId = Math.random();
this._pendingProgress.set(requestId, progress);
try {
return await this._proxy.$invokeAgent(handle, requestId, prompt, { history }, token);
} finally {
this._pendingProgress.delete(requestId);
}
$registerAgent(handle: number, name: string, metadata: IChatAgentMetadata & { subCommands: IChatAgentCommand[] }): void {
const d = this._chatAgentService.registerAgent({
id: name,
metadata: revive(metadata),
invoke: async (request, progress, history, token) => {
const requestId = Math.random();
this._pendingProgress.set(requestId, progress);
try {
const result = await this._proxy.$invokeAgent(handle, requestId, request.message, { history }, token);
return {
followUp: result?.followUp ?? [],
};
} finally {
this._pendingProgress.delete(requestId);
}
},
async provideSlashCommands() {
return metadata.subCommands;
},
});
this._agents.set(handle, d);
}

async $handleProgressChunk(requestId: number, chunk: IChatSlashFragment): Promise<void> {
this._pendingProgress.get(requestId)?.report(revive(chunk));
// An extra step because TS really struggles with type inference in the Revived generic parameter?
const revived = revive<IChatSlashFragment>(chunk);
if (typeof revived.content === 'string') {
this._pendingProgress.get(requestId)?.report({ content: revived.content });
} else {
this._pendingProgress.get(requestId)?.report(revived.content);
}
}

$unregisterCommand(handle: number): void {
Expand Down
80 changes: 80 additions & 0 deletions src/vs/workbench/api/browser/mainThreadChatAgents2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { DisposableMap, IDisposable } from 'vs/base/common/lifecycle';
import { revive } from 'vs/base/common/marshalling';
import { IProgress } from 'vs/platform/progress/common/progress';
import { ExtHostChatAgentsShape2, ExtHostContext, IChatResponseProgressDto, IExtensionChatAgentMetadata, MainContext, MainThreadChatAgentsShape2 } from 'vs/workbench/api/common/extHost.protocol';
import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { IChatProgress } from 'vs/workbench/contrib/chat/common/chatService';
import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers';


type AgentData = {
dispose: () => void;
name: string;
hasSlashCommands?: boolean;
hasFollowups?: boolean;
};

@extHostNamedCustomer(MainContext.MainThreadChatAgents2)
export class MainThreadChatAgents implements MainThreadChatAgentsShape2, IDisposable {

private readonly _agents = new DisposableMap<number, AgentData>;
private readonly _pendingProgress = new Map<number, IProgress<IChatProgress>>();
private readonly _proxy: ExtHostChatAgentsShape2;

constructor(
extHostContext: IExtHostContext,
@IChatAgentService private readonly _chatAgentService: IChatAgentService
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatAgents2);
}

$unregisterAgent(handle: number): void {
this._agents.deleteAndDispose(handle);
}

dispose(): void {
this._agents.clearAndDisposeAll();
}

$registerAgent(handle: number, name: string, metadata: IExtensionChatAgentMetadata): void {
const d = this._chatAgentService.registerAgent({
id: name,
metadata: revive(metadata),
invoke: async (request, progress, history, token) => {
const requestId = Math.random(); // Make this a guid
this._pendingProgress.set(requestId, progress);
try {
return await this._proxy.$invokeAgent(handle, requestId, request, { history }, token) ?? {};
} finally {
this._pendingProgress.delete(requestId);
}
},
provideSlashCommands: async (token) => {
if (!this._agents.get(handle)?.hasSlashCommands) {
return []; // safe an IPC call
}
return this._proxy.$provideSlashCommands(handle, token);
}
});
this._agents.set(handle, { name, dispose: d.dispose, hasSlashCommands: metadata.hasSlashCommands });
}

$updateAgent(handle: number, metadataUpdate: IExtensionChatAgentMetadata): void {
const data = this._agents.get(handle);
if (!data) {
throw new Error(`No agent with handle ${handle} registered`);
}
data.hasSlashCommands = metadataUpdate.hasSlashCommands;
this._chatAgentService.updateAgent(data.name, revive(metadataUpdate));
}

async $handleProgressChunk(requestId: number, chunk: IChatResponseProgressDto): Promise<void> {
// TODO copy/move $acceptResponseProgress from MainThreadChat
this._pendingProgress.get(requestId)?.report(revive(chunk) as any);
}
}
7 changes: 6 additions & 1 deletion src/vs/workbench/api/common/extHost.api.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ import { ExtHostChatVariables } from 'vs/workbench/api/common/extHostChatVariabl
import { ExtHostRelatedInformation } from 'vs/workbench/api/common/extHostAiRelatedInformation';
import { ExtHostAiEmbeddingVector } from 'vs/workbench/api/common/extHostEmbeddingVector';
import { ExtHostChatAgents } from 'vs/workbench/api/common/extHostChatAgents';
import { ExtHostChatAgents2 } from 'vs/workbench/api/common/extHostChatAgents2';

export interface IExtensionRegistries {
mine: ExtensionDescriptionRegistry;
Expand Down Expand Up @@ -209,6 +210,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
const extHostInteractiveEditor = rpcProtocol.set(ExtHostContext.ExtHostInlineChat, new ExtHostInteractiveEditor(rpcProtocol, extHostCommands, extHostDocuments, extHostLogService));
const extHostChatProvider = rpcProtocol.set(ExtHostContext.ExtHostChatProvider, new ExtHostChatProvider(rpcProtocol, extHostLogService));
const extHostChatAgents = rpcProtocol.set(ExtHostContext.ExtHostChatAgents, new ExtHostChatAgents(rpcProtocol, extHostChatProvider, extHostLogService));
const extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostChatProvider, extHostLogService));
const extHostChatVariables = rpcProtocol.set(ExtHostContext.ExtHostChatVariables, new ExtHostChatVariables(rpcProtocol));
const extHostChat = rpcProtocol.set(ExtHostContext.ExtHostChat, new ExtHostChat(rpcProtocol, extHostLogService));
const extHostAiRelatedInformation = rpcProtocol.set(ExtHostContext.ExtHostAiRelatedInformation, new ExtHostRelatedInformation(rpcProtocol));
Expand Down Expand Up @@ -1366,11 +1368,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
checkProposedApiEnabled(extension, 'mappedEditsProvider');
return extHostLanguageFeatures.registerMappedEditsProvider(extension, selector, provider);
},
createChatAgent(name: string, handler: vscode.ChatAgentHandler) {
checkProposedApiEnabled(extension, 'chatAgents2');
return extHostChatAgents2.createChatAgent(extension.identifier, name, handler);
},
registerAgent(name: string, agent: vscode.ChatAgent, metadata: vscode.ChatAgentMetadata) {
checkProposedApiEnabled(extension, 'chatAgents');
return extHostChatAgents.registerAgent(extension.identifier, name, agent, metadata);
}

};

return <typeof vscode>{
Expand Down
24 changes: 22 additions & 2 deletions src/vs/workbench/api/common/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ import * as tasks from 'vs/workbench/api/common/shared/tasks';
import { SaveReason } from 'vs/workbench/common/editor';
import { IRevealOptions, ITreeItem, IViewBadge } from 'vs/workbench/common/views';
import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
import { IChatAgentMetadata } from 'vs/workbench/contrib/chat/common/chatAgents';
import { IChatAgentCommand, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents';
import { IChatMessage, IChatResponseFragment, IChatResponseProviderMetadata } from 'vs/workbench/contrib/chat/common/chatProvider';
import { IChatDynamicRequest, IChatFollowup, IChatReplyFollowup, IChatResponseErrorDetails, IChatUserActionEvent, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatSlashFragment } from 'vs/workbench/contrib/chat/common/chatSlashCommands';
Expand Down Expand Up @@ -1147,15 +1147,33 @@ export interface ExtHostChatProviderShape {
}

export interface MainThreadChatAgentsShape extends IDisposable {
$registerAgent(handle: number, name: string, metadata: IChatAgentMetadata): void;
$registerAgent(handle: number, name: string, metadata: IChatAgentMetadata & { subCommands: IChatAgentCommand[] }): void;
$unregisterAgent(handle: number): void;
$handleProgressChunk(requestId: number, chunk: IChatSlashFragment): Promise<void>;
}

export interface IExtensionChatAgentMetadata extends Dto<IChatAgentMetadata> {
hasSlashCommands?: boolean;
hasFollowup?: boolean;
}

export interface MainThreadChatAgentsShape2 extends IDisposable {
$registerAgent(handle: number, name: string, metadata: IExtensionChatAgentMetadata): void;
$updateAgent(handle: number, metadataUpdate: IExtensionChatAgentMetadata): void;
$unregisterAgent(handle: number): void;
$handleProgressChunk(requestId: number, chunk: IChatResponseProgressDto): Promise<void>;
}

export interface ExtHostChatAgentsShape {
$invokeAgent(handle: number, requestId: number, prompt: string, context: { history: IChatMessage[] }, token: CancellationToken): Promise<any>;
}

export interface ExtHostChatAgentsShape2 {
$invokeAgent(handle: number, requestId: number, request: IChatAgentRequest, context: { history: IChatMessage[] }, token: CancellationToken): Promise<IChatAgentResult | undefined>;
$provideSlashCommands(handle: number, token: CancellationToken): Promise<IChatAgentCommand[]>;
$provideFollowups(handle: number, requestId: number, token: CancellationToken): Promise<IChatFollowup[]>;
}

export interface MainThreadChatVariablesShape extends IDisposable {
$registerVariable(handle: number, data: IChatVariableData): void;
$unregisterVariable(handle: number): void;
Expand Down Expand Up @@ -2664,6 +2682,7 @@ export const MainContext = {
MainThreadBulkEdits: createProxyIdentifier<MainThreadBulkEditsShape>('MainThreadBulkEdits'),
MainThreadChatProvider: createProxyIdentifier<MainThreadChatProviderShape>('MainThreadChatProvider'),
MainThreadChatAgents: createProxyIdentifier<MainThreadChatAgentsShape>('MainThreadChatAgents'),
MainThreadChatAgents2: createProxyIdentifier<MainThreadChatAgentsShape2>('MainThreadChatAgents2'),
MainThreadChatVariables: createProxyIdentifier<MainThreadChatVariablesShape>('MainThreadChatVariables'),
MainThreadClipboard: createProxyIdentifier<MainThreadClipboardShape>('MainThreadClipboard'),
MainThreadCommands: createProxyIdentifier<MainThreadCommandsShape>('MainThreadCommands'),
Expand Down Expand Up @@ -2785,6 +2804,7 @@ export const ExtHostContext = {
ExtHostInlineChat: createProxyIdentifier<ExtHostInlineChatShape>('ExtHostInlineChatShape'),
ExtHostChat: createProxyIdentifier<ExtHostChatShape>('ExtHostChat'),
ExtHostChatAgents: createProxyIdentifier<ExtHostChatAgentsShape>('ExtHostChatAgents'),
ExtHostChatAgents2: createProxyIdentifier<ExtHostChatAgentsShape2>('ExtHostChatAgents'),
ExtHostChatVariables: createProxyIdentifier<ExtHostChatVariablesShape>('ExtHostChatVariables'),
ExtHostChatProvider: createProxyIdentifier<ExtHostChatProviderShape>('ExtHostChatProvider'),
ExtHostAiRelatedInformation: createProxyIdentifier<ExtHostAiRelatedInformationShape>('ExtHostAiRelatedInformation'),
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/api/common/extHostChat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ export class ExtHostChat implements ExtHostChatShape {
}

async $onDidPerformUserAction(event: IChatUserActionEvent): Promise<void> {
this._onDidPerformUserAction.fire(event);
this._onDidPerformUserAction.fire(event as any);
}

//#endregion
Expand Down
Loading