diff --git a/extensions/codestory/src/completions/providers/aideAgentProvider.ts b/extensions/codestory/src/completions/providers/aideAgentProvider.ts new file mode 100644 index 00000000000..ed3adf24c0e --- /dev/null +++ b/extensions/codestory/src/completions/providers/aideAgentProvider.ts @@ -0,0 +1,75 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; + +import { getUserId } from '../../utilities/uniqueId'; + +export class AideAgentSessionProvider implements vscode.Disposable { + private aideAgent: vscode.AideSessionAgent; + + private sessionId: string | undefined; + private stream: vscode.ChatResponseStream | undefined; + private eventQueue: vscode.ChatRequest[] = []; + private processingEvents: Map = new Map(); + + constructor() { + this.aideAgent = vscode.aideAgent.createChatParticipant('aide', { + newSession: this.newSession.bind(this), + handleEvent: this.handleEvent.bind(this), + }); + this.aideAgent.iconPath = vscode.Uri.joinPath( + vscode.extensions.getExtension('codestory-ghost.codestoryai')?.extensionUri ?? vscode.Uri.parse(''), + 'assets', + 'aide-agent.png' + ); + this.aideAgent.requester = { + name: getUserId(), + icon: vscode.Uri.joinPath( + vscode.extensions.getExtension('codestory-ghost.codestoryai')?.extensionUri ?? vscode.Uri.parse(''), + 'assets', + 'aide-user.png' + ) + }; + this.aideAgent.supportIssueReporting = false; + this.aideAgent.welcomeMessageProvider = { + provideWelcomeMessage: async () => { + return [ + 'Hi, I\'m **Aide**, your personal coding assistant! I can find, understand, explain, debug or write code for you.', + ]; + } + }; + } + + private newSession(sessionId: string, stream: vscode.ChatResponseStream): void { + this.sessionId = sessionId; + this.stream = stream; + } + + private async handleEvent(event: vscode.ChatRequest, token: vscode.CancellationToken): Promise { + this.eventQueue.push(event); + if (this.sessionId && !this.processingEvents.has(this.sessionId)) { + this.processingEvents.set(this.sessionId, true); + this.processEvent(event); + } + } + + private async processEvent(event: vscode.ChatRequest): Promise { + await this.generateResponse(event); + if (this.sessionId) { + this.processingEvents.delete(this.sessionId); + } + } + + private async generateResponse(event: vscode.ChatRequest) { + // Simulate processing time + await new Promise(resolve => setTimeout(resolve, 1000)); + this.stream?.markdown('Hello, world! Thanks for telling me ' + event.prompt); + } + + dispose() { + this.aideAgent.dispose(); + } +} diff --git a/extensions/codestory/src/completions/providers/chatprovider.ts b/extensions/codestory/src/completions/providers/chatprovider.ts index 024a017d05e..acdade01cba 100644 --- a/extensions/codestory/src/completions/providers/chatprovider.ts +++ b/extensions/codestory/src/completions/providers/chatprovider.ts @@ -14,10 +14,8 @@ import { InLineAgentContextSelection } from '../../sidecar/types'; import { getSelectedCodeContextForExplain } from '../../utilities/getSelectionContext'; import { getUserId } from '../../utilities/uniqueId'; import { ProjectContext } from '../../utilities/workspaceContext'; -import { registerOpenFiles } from './openFiles'; import { IndentStyleSpaces, IndentationHelper, provideInteractiveEditorResponse } from './editorSessionProvider'; import { AdjustedLineContent, AnswerSplitOnNewLineAccumulator, AnswerStreamContext, AnswerStreamLine, LineContent, LineIndentManager, StateEnum } from './reportEditorSessionAnswerStream'; -import { registerTerminalSelection } from './terminalSelection'; class CSChatParticipant implements vscode.ChatRequesterInformation { name: string; diff --git a/extensions/codestory/src/extension.ts b/extensions/codestory/src/extension.ts index b70166a89a7..23a4e781f51 100644 --- a/extensions/codestory/src/extension.ts +++ b/extensions/codestory/src/extension.ts @@ -6,14 +6,16 @@ import * as os from 'os'; import { commands, DiagnosticSeverity, env, ExtensionContext, languages, modelSelection, window, workspace, } from 'vscode'; import { createInlineCompletionItemProvider } from './completions/create-inline-completion-item-provider'; -import { CSChatAgentProvider } from './completions/providers/chatprovider'; +import { AideAgentSessionProvider } from './completions/providers/aideAgentProvider'; import { AideProbeProvider } from './completions/providers/probeProvider'; +import { CSEventHandler } from './csEvents/csEventHandler'; import { getGitCurrentHash, getGitRepoName } from './git/helper'; import { aideCommands } from './inlineCompletion/commands'; import { startupStatusBar } from './inlineCompletion/statusBar'; import logger from './logger'; import postHogClient from './posthog/client'; import { AideQuickFix } from './quickActions/fix'; +import { RecentEditsRetriever } from './server/editedFiles'; import { RepoRef, RepoRefBackend, SideCarClient } from './sidecar/client'; import { loadOrSaveToStorage } from './storage/types'; import { copySettings } from './utilities/copySettings'; @@ -25,8 +27,6 @@ import { readCustomSystemInstruction } from './utilities/systemInstruction'; import { CodeSymbolInformationEmbeddings } from './utilities/types'; import { getUniqueId } from './utilities/uniqueId'; import { ProjectContext } from './utilities/workspaceContext'; -import { CSEventHandler } from './csEvents/csEventHandler'; -import { RecentEditsRetriever } from './server/editedFiles'; export let SIDECAR_CLIENT: SideCarClient | null = null; @@ -176,12 +176,18 @@ export async function activate(context: ExtensionContext) { const aideQuickFix = new AideQuickFix(); languages.registerCodeActionsProvider('*', aideQuickFix); + /* const chatAgentProvider = new CSChatAgentProvider( rootPath, repoName, repoHash, uniqueUserId, sidecarClient, currentRepo, projectContext ); context.subscriptions.push(chatAgentProvider); + */ + + // Register the agent session provider + const agentSessionProvider = new AideAgentSessionProvider(); + context.subscriptions.push(agentSessionProvider); // add the recent edits retriver to the subscriptions // so we can grab the recent edits very quickly diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index f2af260ba6d..b905646787a 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1494,33 +1494,22 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // namespace: aideAgent const aideAgent: typeof vscode.aideAgent = { - registerChatResponseProvider(id: string, provider: vscode.ChatResponseProvider, metadata: vscode.ChatResponseProviderMetadata) { - checkProposedApiEnabled(extension, 'chatProvider'); - return extHostLanguageModels.registerLanguageModel(extension, id, provider, metadata); + createChatParticipant(id: string, resolver: vscode.AideSessionParticipant) { + checkProposedApiEnabled(extension, 'aideAgent'); + return extHostAideAgentAgents2.createChatAgent(extension, id, resolver); + }, + registerChatParticipantDetectionProvider(provider: vscode.ChatParticipantDetectionProvider) { + checkProposedApiEnabled(extension, 'aideAgent'); + return extHostAideAgentAgents2.registerChatParticipantDetectionProvider(provider); }, registerChatVariableResolver(id: string, name: string, userDescription: string, modelDescription: string | undefined, isSlow: boolean | undefined, resolver: vscode.ChatVariableResolver, fullName?: string, icon?: vscode.ThemeIcon) { - checkProposedApiEnabled(extension, 'chatVariableResolver'); + checkProposedApiEnabled(extension, 'aideAgent'); return extHostAideAgentVariables.registerVariableResolver(extension, id, name, userDescription, modelDescription, isSlow, resolver, fullName, icon?.id); }, - registerMappedEditsProvider(selector: vscode.DocumentSelector, provider: vscode.MappedEditsProvider) { - checkProposedApiEnabled(extension, 'mappedEditsProvider'); - return extHostLanguageFeatures.registerMappedEditsProvider(extension, selector, provider); - }, registerMappedEditsProvider2(provider: vscode.MappedEditsProvider2) { - checkProposedApiEnabled(extension, 'mappedEditsProvider'); + checkProposedApiEnabled(extension, 'aideAgent'); return extHostAideAgentCodeMapper.registerMappedEditsProvider(extension, provider); }, - createChatParticipant(id: string, handler: vscode.ChatExtendedRequestHandler) { - return extHostAideAgentAgents2.createChatAgent(extension, id, handler); - }, - createDynamicChatParticipant(id: string, dynamicProps: vscode.DynamicChatParticipantProps, handler: vscode.ChatExtendedRequestHandler): vscode.ChatParticipant { - checkProposedApiEnabled(extension, 'chatParticipantPrivate'); - return extHostAideAgentAgents2.createDynamicChatAgent(extension, id, dynamicProps, handler); - }, - registerChatParticipantDetectionProvider(provider: vscode.ChatParticipantDetectionProvider) { - checkProposedApiEnabled(extension, 'chatParticipantAdditions'); - return extHostAideAgentAgents2.registerChatParticipantDetectionProvider(provider); - }, }; // namespace: lm diff --git a/src/vs/workbench/api/common/extHostAideAgentAgents2.ts b/src/vs/workbench/api/common/extHostAideAgentAgents2.ts index da30d7e44c6..5511ddeaf04 100644 --- a/src/vs/workbench/api/common/extHostAideAgentAgents2.ts +++ b/src/vs/workbench/api/common/extHostAideAgentAgents2.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import type * as vscode from 'vscode'; import { coalesce } from '../../../base/common/arrays.js'; import { raceCancellation } from '../../../base/common/async.js'; import { CancellationToken } from '../../../base/common/cancellation.js'; @@ -18,16 +19,15 @@ import { URI } from '../../../base/common/uri.js'; import { Location } from '../../../editor/common/languages.js'; import { ExtensionIdentifier, IExtensionDescription } from '../../../platform/extensions/common/extensions.js'; import { ILogService } from '../../../platform/log/common/log.js'; -import { ExtHostChatAgentsShape2, IChatAgentCompletionItem, IChatAgentHistoryEntryDto, IChatProgressDto, IExtensionChatAgentMetadata, IMainContext, MainContext, MainThreadChatAgentsShape2 } from './extHost.protocol.js'; -import { CommandsConverter, ExtHostCommands } from './extHostCommands.js'; -import { ExtHostDocuments } from './extHostDocuments.js'; -import * as typeConvert from './extHostTypeConverters.js'; -import * as extHostTypes from './extHostTypes.js'; import { ChatAgentLocation, IChatAgentRequest, IChatAgentResult, IChatAgentResultTimings } from '../../contrib/aideAgent/common/aideAgentAgents.js'; import { ChatAgentVoteDirection, IChatContentReference, IChatFollowup, IChatResponseErrorDetails, IChatUserActionEvent, IChatVoteAction } from '../../contrib/aideAgent/common/aideAgentService.js'; import { checkProposedApiEnabled, isProposedApiEnabled } from '../../services/extensions/common/extensions.js'; import { Dto } from '../../services/extensions/common/proxyIdentifier.js'; -import type * as vscode from 'vscode'; +import { ExtHostChatAgentsShape2, IChatAgentCompletionItem, IChatAgentHistoryEntryDto, IChatProgressDto, IMainContext, MainContext, MainThreadChatAgentsShape2 } from './extHost.protocol.js'; +import { CommandsConverter, ExtHostCommands } from './extHostCommands.js'; +import { ExtHostDocuments } from './extHostDocuments.js'; +import * as typeConvert from './extHostTypeConverters.js'; +import * as extHostTypes from './extHostTypes.js'; class ChatAgentResponseStream { @@ -299,24 +299,15 @@ export class ExtHostAideAgentAgents2 extends Disposable implements ExtHostChatAg this._proxy.$transferActiveChatSession(newWorkspace); } - createChatAgent(extension: IExtensionDescription, id: string, handler: vscode.ChatExtendedRequestHandler): vscode.ChatParticipant { + createChatAgent(extension: IExtensionDescription, id: string, handler: vscode.AideSessionParticipant): vscode.AideSessionAgent { const handle = ExtHostAideAgentAgents2._idPool++; - const agent = new ExtHostChatAgent(extension, id, this._proxy, handle, handler); + const agent = new ExtHostChatAgent(extension, id, this._proxy, handle, handler.newSession, handler.handleEvent); this._agents.set(handle, agent); this._proxy.$registerAgent(handle, extension.identifier, id, {}, undefined); return agent.apiAgent; } - createDynamicChatAgent(extension: IExtensionDescription, id: string, dynamicProps: vscode.DynamicChatParticipantProps, handler: vscode.ChatExtendedRequestHandler): vscode.ChatParticipant { - const handle = ExtHostAideAgentAgents2._idPool++; - const agent = new ExtHostChatAgent(extension, id, this._proxy, handle, handler); - this._agents.set(handle, agent); - - this._proxy.$registerAgent(handle, extension.identifier, id, { isSticky: true } satisfies IExtensionChatAgentMetadata, dynamicProps); - return agent.apiAgent; - } - registerChatParticipantDetectionProvider(provider: vscode.ChatParticipantDetectionProvider): vscode.Disposable { const handle = ExtHostAideAgentAgents2._participantDetectionProviderIdPool++; this._participantDetectionProviders.set(handle, provider); @@ -375,24 +366,27 @@ export class ExtHostAideAgentAgents2 extends Disposable implements ExtHostChatAg let stream: ChatAgentResponseStream | undefined; try { - const { request, location, history } = await this._createRequest(requestDto, context); + const { request, location } = await this._createRequest(requestDto, context); if (!isProposedApiEnabled(agent.extension, 'chatParticipantAdditions')) { delete request.userSelectedModelId; } // Init session disposables + let newSession = false; let sessionDisposables = this._sessionDisposables.get(request.sessionId); if (!sessionDisposables) { + newSession = true; sessionDisposables = new DisposableStore(); this._sessionDisposables.set(request.sessionId, sessionDisposables); } stream = new ChatAgentResponseStream(agent.extension, request, this._proxy, this._commands.converter, sessionDisposables); + if (newSession) { + agent.createSession(request.sessionId, stream.apiObject); + } const task = agent.invoke( typeConvert.ChatAgentRequest.to(request, location), - { history }, - stream.apiObject, token ); @@ -602,9 +596,14 @@ class ExtHostChatAgent { public readonly id: string, private readonly _proxy: MainThreadChatAgentsShape2, private readonly _handle: number, - private _requestHandler: vscode.ChatExtendedRequestHandler, + private _sessionHandler: vscode.AideSessionHandler, + private _requestHandler: vscode.AideSessionEventHandler, ) { } + createSession(sessionId: string, stream: vscode.ChatResponseStream): void { + this._sessionHandler(sessionId, stream); + } + acceptFeedback(feedback: vscode.ChatResultFeedback) { this._onDidReceiveFeedback.fire(feedback); } @@ -674,7 +673,7 @@ class ExtHostChatAgent { return content; } - get apiAgent(): vscode.ChatParticipant { + get apiAgent(): vscode.AideSessionAgent { let disposed = false; let updateScheduled = false; const updateMetadataSoon = () => { @@ -843,10 +842,10 @@ class ExtHostChatAgent { that._onDidReceiveFeedback.dispose(); that._proxy.$unregisterAgent(that._handle); }, - } satisfies vscode.ChatParticipant; + } satisfies vscode.AideSessionAgent; } - invoke(request: vscode.ChatRequest, context: vscode.ChatContext, response: vscode.ChatResponseStream, token: CancellationToken): vscode.ProviderResult { - return this._requestHandler(request, context, response, token); + invoke(request: vscode.ChatRequest, token: CancellationToken): vscode.ProviderResult { + return this._requestHandler(request, token); } } diff --git a/src/vscode-dts/vscode.proposed.aideAgent.d.ts b/src/vscode-dts/vscode.proposed.aideAgent.d.ts index e2f57169cce..7cb6f89508d 100644 --- a/src/vscode-dts/vscode.proposed.aideAgent.d.ts +++ b/src/vscode-dts/vscode.proposed.aideAgent.d.ts @@ -4,13 +4,38 @@ *--------------------------------------------------------------------------------------------*/ declare module 'vscode' { + export type AideSessionHandler = (id: string, stream: ChatResponseStream) => void; + export type AideSessionEventHandler = (event: ChatRequest, token: CancellationToken) => ProviderResult; + + export interface AideSessionParticipant { + newSession: AideSessionHandler; + handleEvent: AideSessionEventHandler; + } + + interface AideSessionAgent { + readonly id: string; + followupProvider?: ChatFollowupProvider; + helpTextPostfix?: string | MarkdownString; + helpTextPrefix?: string | MarkdownString; + helpTextVariablesPrefix?: string | MarkdownString; + iconPath?: Uri | { light: Uri; dark: Uri } | ThemeIcon; + isSecondary?: boolean; + onDidPerformAction: Event; + onDidReceiveFeedback: Event; + participantVariableProvider?: { provider: ChatParticipantCompletionItemProvider; triggerCharacters: string[] }; + requester?: ChatRequesterInformation; + requestHandler: AideSessionEventHandler; + supportIssueReporting?: boolean; + supportsSlowReferences?: boolean; + titleProvider?: ChatTitleProvider; + welcomeMessageProvider?: ChatWelcomeMessageProvider; + dispose(): void; + } + export namespace aideAgent { - export function createChatParticipant(id: string, handler: ChatExtendedRequestHandler): ChatParticipant; - export function createDynamicChatParticipant(id: string, dynamicProps: DynamicChatParticipantProps, handler: ChatExtendedRequestHandler): ChatParticipant; + export function createChatParticipant(id: string, resolver: AideSessionParticipant): AideSessionAgent; export function registerChatParticipantDetectionProvider(participantDetectionProvider: ChatParticipantDetectionProvider): Disposable; - export function registerChatResponseProvider(id: string, provider: ChatResponseProvider, metadata: ChatResponseProviderMetadata): Disposable; export function registerChatVariableResolver(id: string, name: string, userDescription: string, modelDescription: string | undefined, isSlow: boolean | undefined, resolver: ChatVariableResolver, fullName?: string, icon?: ThemeIcon): Disposable; - export function registerMappedEditsProvider(documentSelector: DocumentSelector, provider: MappedEditsProvider): Disposable; export function registerMappedEditsProvider2(provider: MappedEditsProvider2): Disposable; } }