Chat code cleanup (#198022)

* Clean up some obsolete chat API
Start deleting the interactive session provider, use agents only

* Delete old chat agents API

* Remove providerRequestId

* Remove unused stuff from interactive.d.ts

* Get rid of chat session state saving

* Fix test

* I guess this type was in use
This commit is contained in:
Rob Lourens 2023-11-11 23:44:14 -06:00 committed by GitHub
parent bdc113ffe1
commit 7cbff1919e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 292 additions and 1148 deletions

View file

@ -5,7 +5,7 @@
import * as assert from 'assert';
import 'mocha';
import { CancellationToken, chat, ChatAgentRequest, ChatVariableLevel, CompletionItemKind, Disposable, interactive, InteractiveProgress, InteractiveRequest, InteractiveResponseForProgress, InteractiveSession, InteractiveSessionState, Progress, ProviderResult } from 'vscode';
import { CancellationToken, chat, ChatAgentRequest, ChatVariableLevel, Disposable, interactive, InteractiveSession, ProviderResult } from 'vscode';
import { assertNoRpc, closeAllEditors, DeferredPromise, disposeAll } from '../utils';
suite('chat', () => {
@ -22,24 +22,12 @@ suite('chat', () => {
function getDeferredForRequest(): DeferredPromise<ChatAgentRequest> {
disposables.push(interactive.registerInteractiveSessionProvider('provider', {
prepareSession: (_initialState: InteractiveSessionState | undefined, _token: CancellationToken): ProviderResult<InteractiveSession> => {
prepareSession: (_token: CancellationToken): ProviderResult<InteractiveSession> => {
return {
requester: { name: 'test' },
responder: { name: 'test' },
};
},
provideResponseWithProgress: (_request: InteractiveRequest, _progress: Progress<InteractiveProgress>, _token: CancellationToken): ProviderResult<InteractiveResponseForProgress> => {
return null;
},
provideSlashCommands: (_session, _token) => {
return [{ command: 'hello', title: 'Hello', kind: CompletionItemKind.Text }];
},
removeRequest: (_session: InteractiveSession, _requestId: string): void => {
throw new Error('Function not implemented.');
}
}));
const deferred = new DeferredPromise<ChatAgentRequest>();

View file

@ -1,62 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import 'mocha';
import { CancellationToken, CompletionItemKind, Disposable, interactive, InteractiveProgress, InteractiveRequest, InteractiveResponseForProgress, InteractiveSession, InteractiveSessionState, Progress, ProviderResult } from 'vscode';
import { assertNoRpc, closeAllEditors, DeferredPromise, disposeAll } from '../utils';
suite('InteractiveSessionProvider', () => {
let disposables: Disposable[] = [];
setup(async () => {
disposables = [];
});
teardown(async function () {
assertNoRpc();
await closeAllEditors();
disposeAll(disposables);
});
function getDeferredForRequest(): DeferredPromise<InteractiveRequest> {
const deferred = new DeferredPromise<InteractiveRequest>();
disposables.push(interactive.registerInteractiveSessionProvider('provider', {
prepareSession: (_initialState: InteractiveSessionState | undefined, _token: CancellationToken): ProviderResult<InteractiveSession> => {
return {
requester: { name: 'test' },
responder: { name: 'test' },
};
},
provideResponseWithProgress: (request: InteractiveRequest, _progress: Progress<InteractiveProgress>, _token: CancellationToken): ProviderResult<InteractiveResponseForProgress> => {
deferred.complete(request);
return null;
},
provideSlashCommands: (_session, _token) => {
return [{ command: 'hello', title: 'Hello', kind: CompletionItemKind.Text }];
},
removeRequest: (_session: InteractiveSession, _requestId: string): void => {
throw new Error('Function not implemented.');
}
}));
return deferred;
}
test('plain text query', async () => {
const deferred = getDeferredForRequest();
interactive.sendInteractiveRequestToProvider('provider', { message: 'hello' });
const lastResult = await deferred.p;
assert.strictEqual(lastResult.message, 'hello');
});
test('slash command', async () => {
const deferred = getDeferredForRequest();
interactive.sendInteractiveRequestToProvider('provider', { message: '/hello' });
const lastResult = await deferred.p;
assert.strictEqual(lastResult.message, '/hello');
});
});

View file

@ -781,6 +781,10 @@ export class DisposableMap<K, V extends IDisposable = IDisposable> implements ID
return this._store.keys();
}
values(): IterableIterator<V> {
return this._store.values();
}
[Symbol.iterator](): IterableIterator<[K, V]> {
return this._store[Symbol.iterator]();
}

View file

@ -20,7 +20,6 @@ import { StatusBarItemsExtensionPoint } from 'vs/workbench/api/browser/statusBar
import './mainThreadLocalization';
import './mainThreadBulkEdits';
import './mainThreadChatProvider';
import './mainThreadChatAgents';
import './mainThreadChatAgents2';
import './mainThreadChatVariables';
import './mainThreadCodeInsets';

View file

@ -3,31 +3,23 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DeferredPromise } from 'vs/base/common/async';
import { Emitter } from 'vs/base/common/event';
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { Disposable, DisposableMap } from 'vs/base/common/lifecycle';
import { revive } from 'vs/base/common/marshalling';
import { URI, UriComponents } from 'vs/base/common/uri';
import { ExtHostChatShape, ExtHostContext, IChatRequestDto, IChatResponseProgressDto, ILocationDto, MainContext, MainThreadChatShape } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostChatShape, ExtHostContext, MainContext, MainThreadChatShape } from 'vs/workbench/api/common/extHost.protocol';
import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService';
import { isCompleteInteractiveProgressTreeData } from 'vs/workbench/contrib/chat/common/chatModel';
import { IChat, IChatDynamicRequest, IChatProgress, IChatResponse, IChatResponseProgressFileTreeData, IChatService } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatDynamicRequest, IChatService } from 'vs/workbench/contrib/chat/common/chatService';
import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers';
@extHostNamedCustomer(MainContext.MainThreadChat)
export class MainThreadChat extends Disposable implements MainThreadChatShape {
private readonly _providerRegistrations = this._register(new DisposableMap<number>());
private readonly _activeRequestProgressCallbacks = new Map<string, (progress: IChatProgress) => (DeferredPromise<string | IMarkdownString> | void)>();
private readonly _stateEmitters = new Map<number, Emitter<any>>();
private readonly _proxy: ExtHostChatShape;
private _responsePartHandlePool = 0;
private readonly _activeResponsePartPromises = new Map<string, DeferredPromise<string | IMarkdownString | { treeData: IChatResponseProgressFileTreeData }>>();
constructor(
extHostContext: IExtHostContext,
@IChatService private readonly _chatService: IChatService,
@ -64,8 +56,8 @@ export class MainThreadChat extends Disposable implements MainThreadChatShape {
const unreg = this._chatService.registerProvider({
id,
displayName: registration.label,
prepareSession: async (initialState, token) => {
const session = await this._proxy.$prepareChat(handle, initialState, token);
prepareSession: async (token) => {
const session = await this._proxy.$prepareChat(handle, token);
if (!session) {
return undefined;
}
@ -75,14 +67,13 @@ export class MainThreadChat extends Disposable implements MainThreadChatShape {
const emitter = new Emitter<any>();
this._stateEmitters.set(session.id, emitter);
return <IChat>{
return {
id: session.id,
requesterUsername: session.requesterUsername,
requesterAvatarIconUri: URI.revive(session.requesterAvatarIconUri),
responderUsername: session.responderUsername,
responderAvatarIconUri,
inputPlaceholder: session.inputPlaceholder,
onDidChangeState: emitter.event,
dispose: () => {
emitter.dispose();
this._stateEmitters.delete(session.id);
@ -90,87 +81,17 @@ export class MainThreadChat extends Disposable implements MainThreadChatShape {
}
};
},
provideReply: async (request, progress, token) => {
const id = `${handle}_${request.session.id}`;
this._activeRequestProgressCallbacks.set(id, progress);
try {
const requestDto: IChatRequestDto = {
message: request.message,
variables: request.variables
};
const dto = await this._proxy.$provideReply(handle, request.session.id, requestDto, token);
return <IChatResponse>{
session: request.session,
...dto
};
} finally {
this._activeRequestProgressCallbacks.delete(id);
}
},
provideWelcomeMessage: (token) => {
return this._proxy.$provideWelcomeMessage(handle, token);
},
provideSampleQuestions: (token) => {
return this._proxy.$provideSampleQuestions(handle, token);
},
provideSlashCommands: (session, token) => {
return this._proxy.$provideSlashCommands(handle, session.id, token);
},
provideFollowups: (session, token) => {
return this._proxy.$provideFollowups(handle, session.id, token);
},
removeRequest: (session, requestId) => {
return this._proxy.$removeRequest(handle, session.id, requestId);
}
});
this._providerRegistrations.set(handle, unreg);
}
async $acceptResponseProgress(handle: number, sessionId: number, progress: IChatResponseProgressDto, responsePartHandle?: number): Promise<number | void> {
const id = `${handle}_${sessionId}`;
if ('placeholder' in progress) {
const responsePartId = `${id}_${++this._responsePartHandlePool}`;
const deferredContentPromise = new DeferredPromise<string | IMarkdownString | { treeData: IChatResponseProgressFileTreeData }>();
this._activeResponsePartPromises.set(responsePartId, deferredContentPromise);
this._activeRequestProgressCallbacks.get(id)?.({ ...progress, resolvedContent: deferredContentPromise.p });
return this._responsePartHandlePool;
} else if (responsePartHandle) {
// Complete an existing deferred promise with resolved content
const responsePartId = `${id}_${responsePartHandle}`;
const deferredContentPromise = this._activeResponsePartPromises.get(responsePartId);
if (deferredContentPromise && isCompleteInteractiveProgressTreeData(progress)) {
const withRevivedUris = revive<{ treeData: IChatResponseProgressFileTreeData }>(progress);
deferredContentPromise.complete(withRevivedUris);
this._activeResponsePartPromises.delete(responsePartId);
} else if (deferredContentPromise && 'content' in progress) {
deferredContentPromise.complete(progress.content);
this._activeResponsePartPromises.delete(responsePartId);
}
return;
}
// No need to support standalone tree data that's not attached to a placeholder in API
if (isCompleteInteractiveProgressTreeData(progress)) {
return;
}
// TS won't let us change the type of `progress`
let revivedProgress: IChatProgress;
if ('documents' in progress) {
revivedProgress = { documents: revive(progress.documents) };
} else if ('reference' in progress) {
revivedProgress = revive<{ reference: UriComponents | ILocationDto }>(progress);
} else if ('inlineReference' in progress) {
revivedProgress = revive<{ inlineReference: UriComponents | ILocationDto; name?: string }>(progress);
} else {
revivedProgress = progress;
}
this._activeRequestProgressCallbacks.get(id)?.(revivedProgress);
}
async $acceptChatState(sessionId: number, state: any): Promise<void> {
this._stateEmitters.get(sessionId)?.fire(state);
}

View file

@ -1,75 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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 { 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';
@extHostNamedCustomer(MainContext.MainThreadChatAgents)
export class MainThreadChatAgents implements MainThreadChatAgentsShape {
private readonly _agents = new DisposableMap<number>;
private readonly _pendingProgress = new Map<number, IProgress<IChatProgress>>();
private readonly _proxy: ExtHostChatAgentsShape;
constructor(
extHostContext: IExtHostContext,
@IChatAgentService private readonly _chatAgentService: IChatAgentService
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatAgents);
}
$unregisterAgent(handle: number): void {
this._agents.deleteAndDispose(handle);
}
dispose(): void {
this._agents.clearAndDisposeAll();
}
$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, { report: progress });
try {
const message = request.command ? `/${request.command} ${request.message}` : request.message;
const result = await this._proxy.$invokeAgent(handle, requestId, 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> {
// 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 {
this._agents.deleteAndDispose(handle);
}
}

View file

@ -6,110 +6,109 @@
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import * as errors from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { combinedDisposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import Severity from 'vs/base/common/severity';
import { URI } from 'vs/base/common/uri';
import { TextEditorCursorStyle } from 'vs/editor/common/config/editorOptions';
import { OverviewRulerLane } from 'vs/editor/common/model';
import * as languageConfiguration from 'vs/editor/common/languages/languageConfiguration';
import { score } from 'vs/editor/common/languageSelector';
import * as languageConfiguration from 'vs/editor/common/languages/languageConfiguration';
import { OverviewRulerLane } from 'vs/editor/common/model';
import { ExtensionIdentifierSet, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import * as files from 'vs/platform/files/common/files';
import { ExtHostContext, MainContext, CandidatePortSource, ExtHostLogLevelServiceShape } from 'vs/workbench/api/common/extHost.protocol';
import { UIKind } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ILogService, ILoggerService, LogLevel } from 'vs/platform/log/common/log';
import { matchesScheme } from 'vs/platform/opener/common/opener';
import { getRemoteName } from 'vs/platform/remote/common/remoteHosts';
import { TelemetryTrustedValue } from 'vs/platform/telemetry/common/telemetryUtils';
import { EditSessionIdentityMatch } from 'vs/platform/workspace/common/editSessions';
import { CandidatePortSource, ExtHostContext, ExtHostLogLevelServiceShape, MainContext } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostRelatedInformation } from 'vs/workbench/api/common/extHostAiRelatedInformation';
import { ExtHostApiCommands } from 'vs/workbench/api/common/extHostApiCommands';
import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService';
import { ExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication';
import { ExtHostBulkEdits } from 'vs/workbench/api/common/extHostBulkEdits';
import { ExtHostChat } from 'vs/workbench/api/common/extHostChat';
import { ExtHostChatAgents2 } from 'vs/workbench/api/common/extHostChatAgents2';
import { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider';
import { ExtHostChatVariables } from 'vs/workbench/api/common/extHostChatVariables';
import { ExtHostClipboard } from 'vs/workbench/api/common/extHostClipboard';
import { ExtHostEditorInsets } from 'vs/workbench/api/common/extHostCodeInsets';
import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { createExtHostComments } from 'vs/workbench/api/common/extHostComments';
import { ExtHostConfigProvider, IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration';
import { ExtHostCustomEditors } from 'vs/workbench/api/common/extHostCustomEditors';
import { IExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService';
import { IExtHostDecorations } from 'vs/workbench/api/common/extHostDecorations';
import { ExtHostDiagnostics } from 'vs/workbench/api/common/extHostDiagnostics';
import { ExtHostDialogs } from 'vs/workbench/api/common/extHostDialogs';
import { ExtHostDocumentContentProvider } from 'vs/workbench/api/common/extHostDocumentContentProviders';
import { ExtHostDocumentSaveParticipant } from 'vs/workbench/api/common/extHostDocumentSaveParticipant';
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
import { IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { IExtHostEditorTabs } from 'vs/workbench/api/common/extHostEditorTabs';
import { ExtHostAiEmbeddingVector } from 'vs/workbench/api/common/extHostEmbeddingVector';
import { Extension, IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService';
import { ExtHostFileSystem } from 'vs/workbench/api/common/extHostFileSystem';
import { IExtHostConsumerFileSystem } from 'vs/workbench/api/common/extHostFileSystemConsumer';
import { ExtHostFileSystemEventService, FileSystemWatcherCreateOptions } from 'vs/workbench/api/common/extHostFileSystemEventService';
import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
import { ExtHostInteractiveEditor } from 'vs/workbench/api/common/extHostInlineChat';
import { ExtHostInteractive } from 'vs/workbench/api/common/extHostInteractive';
import { ExtHostIssueReporter } from 'vs/workbench/api/common/extHostIssueReporter';
import { ExtHostLabelService } from 'vs/workbench/api/common/extHostLabelService';
import { ExtHostLanguageFeatures } from 'vs/workbench/api/common/extHostLanguageFeatures';
import { ExtHostLanguages } from 'vs/workbench/api/common/extHostLanguages';
import { IExtHostLocalizationService } from 'vs/workbench/api/common/extHostLocalizationService';
import { IExtHostManagedSockets } from 'vs/workbench/api/common/extHostManagedSockets';
import { ExtHostMessageService } from 'vs/workbench/api/common/extHostMessageService';
import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook';
import { ExtHostNotebookDocumentSaveParticipant } from 'vs/workbench/api/common/extHostNotebookDocumentSaveParticipant';
import { ExtHostNotebookDocuments } from 'vs/workbench/api/common/extHostNotebookDocuments';
import { ExtHostNotebookEditors } from 'vs/workbench/api/common/extHostNotebookEditors';
import { ExtHostNotebookKernels } from 'vs/workbench/api/common/extHostNotebookKernels';
import { ExtHostNotebookRenderers } from 'vs/workbench/api/common/extHostNotebookRenderers';
import { IExtHostOutputService } from 'vs/workbench/api/common/extHostOutput';
import { ExtHostProfileContentHandlers } from 'vs/workbench/api/common/extHostProfileContentHandler';
import { ExtHostProgress } from 'vs/workbench/api/common/extHostProgress';
import { ExtHostQuickDiff } from 'vs/workbench/api/common/extHostQuickDiff';
import { createExtHostQuickOpen } from 'vs/workbench/api/common/extHostQuickOpen';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { ExtHostSCM } from 'vs/workbench/api/common/extHostSCM';
import { IExtHostSearch } from 'vs/workbench/api/common/extHostSearch';
import { IExtHostSecretState } from 'vs/workbench/api/common/extHostSecretState';
import { ExtHostShare } from 'vs/workbench/api/common/extHostShare';
import { ExtHostSpeech } from 'vs/workbench/api/common/extHostSpeech';
import { ExtHostStatusBar } from 'vs/workbench/api/common/extHostStatusBar';
import { IExtHostStorage } from 'vs/workbench/api/common/extHostStorage';
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
import { IExtHostTask } from 'vs/workbench/api/common/extHostTask';
import { ExtHostTelemetryLogger, IExtHostTelemetry, isNewAppInstall } from 'vs/workbench/api/common/extHostTelemetry';
import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService';
import { ExtHostTesting } from 'vs/workbench/api/common/extHostTesting';
import { ExtHostEditors } from 'vs/workbench/api/common/extHostTextEditors';
import { ExtHostTheming } from 'vs/workbench/api/common/extHostTheming';
import { ExtHostTimeline } from 'vs/workbench/api/common/extHostTimeline';
import { ExtHostTreeViews } from 'vs/workbench/api/common/extHostTreeViews';
import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService';
import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters';
import * as extHostTypes from 'vs/workbench/api/common/extHostTypes';
import { TelemetryTrustedValue } from 'vs/platform/telemetry/common/telemetryUtils';
import { ExtHostUriOpeners } from 'vs/workbench/api/common/extHostUriOpener';
import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService';
import { ExtHostUrls } from 'vs/workbench/api/common/extHostUrls';
import { ExtHostWebviews } from 'vs/workbench/api/common/extHostWebview';
import { ExtHostWebviewPanels } from 'vs/workbench/api/common/extHostWebviewPanels';
import { ExtHostWebviewViews } from 'vs/workbench/api/common/extHostWebviewView';
import { IExtHostWindow } from 'vs/workbench/api/common/extHostWindow';
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier';
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
import type * as vscode from 'vscode';
import { ExtensionIdentifierSet, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { ExtHostEditorInsets } from 'vs/workbench/api/common/extHostCodeInsets';
import { ExtHostLabelService } from 'vs/workbench/api/common/extHostLabelService';
import { getRemoteName } from 'vs/platform/remote/common/remoteHosts';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IExtHostDecorations } from 'vs/workbench/api/common/extHostDecorations';
import { IExtHostTask } from 'vs/workbench/api/common/extHostTask';
import { IExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService';
import { IExtHostSearch } from 'vs/workbench/api/common/extHostSearch';
import { ILoggerService, ILogService, LogLevel } from 'vs/platform/log/common/log';
import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook';
import { ExtHostTheming } from 'vs/workbench/api/common/extHostTheming';
import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService';
import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService';
import { ExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication';
import { ExtHostTimeline } from 'vs/workbench/api/common/extHostTimeline';
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
import { IExtHostConsumerFileSystem } from 'vs/workbench/api/common/extHostFileSystemConsumer';
import { ExtHostWebviewViews } from 'vs/workbench/api/common/extHostWebviewView';
import { ExtHostCustomEditors } from 'vs/workbench/api/common/extHostCustomEditors';
import { ExtHostWebviewPanels } from 'vs/workbench/api/common/extHostWebviewPanels';
import { ExtHostBulkEdits } from 'vs/workbench/api/common/extHostBulkEdits';
import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo';
import { ExtHostTesting } from 'vs/workbench/api/common/extHostTesting';
import { ExtHostUriOpeners } from 'vs/workbench/api/common/extHostUriOpener';
import { IExtHostSecretState } from 'vs/workbench/api/common/extHostSecretState';
import { IExtHostEditorTabs } from 'vs/workbench/api/common/extHostEditorTabs';
import { ExtHostTelemetryLogger, IExtHostTelemetry, isNewAppInstall } from 'vs/workbench/api/common/extHostTelemetry';
import { ExtHostNotebookKernels } from 'vs/workbench/api/common/extHostNotebookKernels';
import { TextSearchCompleteMessageType } from 'vs/workbench/services/search/common/searchExtTypes';
import { ExtHostNotebookRenderers } from 'vs/workbench/api/common/extHostNotebookRenderers';
import { Schemas } from 'vs/base/common/network';
import { matchesScheme } from 'vs/platform/opener/common/opener';
import { ExtHostNotebookEditors } from 'vs/workbench/api/common/extHostNotebookEditors';
import { ExtHostNotebookDocuments } from 'vs/workbench/api/common/extHostNotebookDocuments';
import { ExtHostInteractive } from 'vs/workbench/api/common/extHostInteractive';
import { combinedDisposable } from 'vs/base/common/lifecycle';
import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
import { DebugConfigurationProviderTriggerKind } from 'vs/workbench/contrib/debug/common/debug';
import { IExtHostLocalizationService } from 'vs/workbench/api/common/extHostLocalizationService';
import { EditSessionIdentityMatch } from 'vs/platform/workspace/common/editSessions';
import { ExtHostProfileContentHandlers } from 'vs/workbench/api/common/extHostProfileContentHandler';
import { ExtHostQuickDiff } from 'vs/workbench/api/common/extHostQuickDiff';
import { ExtHostChat } from 'vs/workbench/api/common/extHostChat';
import { ExtHostInteractiveEditor } from 'vs/workbench/api/common/extHostInlineChat';
import { ExtHostNotebookDocumentSaveParticipant } from 'vs/workbench/api/common/extHostNotebookDocumentSaveParticipant';
import { ExtHostIssueReporter } from 'vs/workbench/api/common/extHostIssueReporter';
import { IExtHostManagedSockets } from 'vs/workbench/api/common/extHostManagedSockets';
import { ExtHostShare } from 'vs/workbench/api/common/extHostShare';
import { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider';
import { ExtHostSpeech } from 'vs/workbench/api/common/extHostSpeech';
import { ExtHostChatVariables } from 'vs/workbench/api/common/extHostChatVariables';
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';
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
import { UIKind } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier';
import { TextSearchCompleteMessageType } from 'vs/workbench/services/search/common/searchExtTypes';
import type * as vscode from 'vscode';
export interface IExtensionRegistries {
mine: ExtensionDescriptionRegistry;
@ -210,10 +209,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
rpcProtocol.set(ExtHostContext.ExtHostInteractive, new ExtHostInteractive(rpcProtocol, extHostNotebook, extHostDocumentsAndEditors, extHostCommands, extHostLogService));
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 extHostChat = rpcProtocol.set(ExtHostContext.ExtHostChat, new ExtHostChat(rpcProtocol));
const extHostAiRelatedInformation = rpcProtocol.set(ExtHostContext.ExtHostAiRelatedInformation, new ExtHostRelatedInformation(rpcProtocol));
const extHostAiEmbeddingVector = rpcProtocol.set(ExtHostContext.ExtHostAiEmbeddingVector, new ExtHostAiEmbeddingVector(rpcProtocol));
const extHostIssueReporter = rpcProtocol.set(ExtHostContext.ExtHostIssueReporter, new ExtHostIssueReporter(rpcProtocol));
@ -1382,10 +1380,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
checkProposedApiEnabled(extension, 'chatAgents2');
return extHostChatAgents2.createChatAgent(extension, name, handler);
},
registerAgent(name: string, agent: vscode.ChatAgent, metadata: vscode.ChatAgentMetadata) {
checkProposedApiEnabled(extension, 'chatAgents');
return extHostChatAgents.registerAgent(extension.identifier, name, agent, metadata);
}
};
// namespace: speech

View file

@ -52,8 +52,7 @@ import { IRevealOptions, ITreeItem, IViewBadge } from 'vs/workbench/common/views
import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
import { IChatAgentCommand, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents';
import { IChatMessage, IChatResponseFragment, IChatResponseProviderMetadata } from 'vs/workbench/contrib/chat/common/chatProvider';
import { IChatAgentDetection, IChatDynamicRequest, IChatFollowup, IChatReplyFollowup, IChatResponseErrorDetails, IChatUserActionEvent, ISlashCommand, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatSlashFragment } from 'vs/workbench/contrib/chat/common/chatSlashCommands';
import { IChatAgentDetection, IChatDynamicRequest, IChatFollowup, IChatReplyFollowup, IChatResponseErrorDetails, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatRequestVariableValue, IChatVariableData } from 'vs/workbench/contrib/chat/common/chatVariables';
import { DebugConfigurationProviderTriggerKind, IAdapterDescriptor, IConfig, IDebugSessionReplMode } from 'vs/workbench/contrib/debug/common/debug';
import { IInlineChatBulkEditResponse, IInlineChatEditResponse, IInlineChatMessageResponse, IInlineChatProgressItem, IInlineChatRequest, IInlineChatSession, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
@ -63,6 +62,7 @@ import { ICellExecutionComplete, ICellExecutionStateUpdate } from 'vs/workbench/
import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange';
import { InputValidationType } from 'vs/workbench/contrib/scm/common/scm';
import { IWorkspaceSymbol, NotebookPriorityInfo } from 'vs/workbench/contrib/search/common/search';
import { IRawClosedNotebookFileMatch } from 'vs/workbench/contrib/search/common/searchNotebookHelpers';
import { ISpeechProviderMetadata, ISpeechToTextEvent } from 'vs/workbench/contrib/speech/common/speechService';
import { CoverageDetails, ExtensionRunTestsRequest, ICallProfileRunHandler, IFileCoverage, ISerializedTestResults, IStartControllerTests, ITestItem, ITestMessage, ITestRunProfile, ITestRunTask, ResolvedTestRunRequest, TestResultState, TestsDiffOp } from 'vs/workbench/contrib/testing/common/testTypes';
import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline';
@ -80,7 +80,6 @@ import { CandidatePort } from 'vs/workbench/services/remote/common/tunnelModel';
import { ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder';
import * as search from 'vs/workbench/services/search/common/search';
import { ISaveProfileResult } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
import { IRawClosedNotebookFileMatch } from 'vs/workbench/contrib/search/common/searchNotebookHelpers';
export interface IWorkspaceData extends IStaticWorkspaceData {
folders: { uri: UriComponents; name: string; index: number }[];
@ -1166,12 +1165,6 @@ export interface ExtHostChatProviderShape {
$handleResponseFragment(requestId: number, chunk: IChatResponseFragment): Promise<void>;
}
export interface MainThreadChatAgentsShape extends IDisposable {
$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;
@ -1184,10 +1177,6 @@ export interface MainThreadChatAgentsShape2 extends IDisposable {
$handleProgressChunk(requestId: string, chunk: IChatResponseProgressDto, responsePartHandle?: number): Promise<number | void>;
}
export interface ExtHostChatAgentsShape {
$invokeAgent(handle: number, requestId: number, prompt: string, context: { history: IChatMessage[] }, token: CancellationToken): Promise<any>;
}
export interface ExtHostChatAgentsShape2 {
$invokeAgent(handle: number, sessionId: string, requestId: string, request: IChatAgentRequest, context: { history: IChatMessage[] }, token: CancellationToken): Promise<IChatAgentResult | undefined>;
$provideSlashCommands(handle: number, token: CancellationToken): Promise<IChatAgentCommand[]>;
@ -1263,7 +1252,6 @@ export type IDocumentContextDto = {
export type IChatResponseProgressDto =
| { content: string | IMarkdownString }
| { requestId: string }
| { placeholder: string }
| { treeData: IChatResponseProgressFileTreeData }
| { documents: IDocumentContextDto[] }
@ -1276,18 +1264,13 @@ export interface MainThreadChatShape extends IDisposable {
$acceptChatState(sessionId: number, state: any): Promise<void>;
$sendRequestToProvider(providerId: string, message: IChatDynamicRequest): void;
$unregisterChatProvider(handle: number): Promise<void>;
$acceptResponseProgress(handle: number, sessionId: number, progress: IChatResponseProgressDto, responsePartHandle?: number): Promise<number | void>;
$transferChatSession(sessionId: number, toWorkspace: UriComponents): void;
}
export interface ExtHostChatShape {
$prepareChat(handle: number, initialState: any, token: CancellationToken): Promise<IChatDto | undefined>;
$prepareChat(handle: number, token: CancellationToken): Promise<IChatDto | undefined>;
$provideWelcomeMessage(handle: number, token: CancellationToken): Promise<(string | IMarkdownString | IChatReplyFollowup[])[] | undefined>;
$provideSampleQuestions(handle: number, token: CancellationToken): Promise<IChatReplyFollowup[] | undefined>;
$provideFollowups(handle: number, sessionId: number, token: CancellationToken): Promise<IChatFollowup[] | undefined>;
$provideReply(handle: number, sessionId: number, request: IChatRequestDto, token: CancellationToken): Promise<IChatResponseDto | undefined>;
$removeRequest(handle: number, sessionId: number, requestId: string): void;
$provideSlashCommands(handle: number, sessionId: number, token: CancellationToken): Promise<ISlashCommand[] | undefined>;
$releaseSession(sessionId: number): void;
$onDidPerformUserAction(event: IChatUserActionEvent): Promise<void>;
}
@ -2721,7 +2704,6 @@ export const MainContext = {
MainThreadAuthentication: createProxyIdentifier<MainThreadAuthenticationShape>('MainThreadAuthentication'),
MainThreadBulkEdits: createProxyIdentifier<MainThreadBulkEditsShape>('MainThreadBulkEdits'),
MainThreadChatProvider: createProxyIdentifier<MainThreadChatProviderShape>('MainThreadChatProvider'),
MainThreadChatAgents: createProxyIdentifier<MainThreadChatAgentsShape>('MainThreadChatAgents'),
MainThreadChatAgents2: createProxyIdentifier<MainThreadChatAgentsShape2>('MainThreadChatAgents2'),
MainThreadChatVariables: createProxyIdentifier<MainThreadChatVariablesShape>('MainThreadChatVariables'),
MainThreadClipboard: createProxyIdentifier<MainThreadClipboardShape>('MainThreadClipboard'),
@ -2844,7 +2826,6 @@ export const ExtHostContext = {
ExtHostInteractive: createProxyIdentifier<ExtHostInteractiveShape>('ExtHostInteractive'),
ExtHostInlineChat: createProxyIdentifier<ExtHostInlineChatShape>('ExtHostInlineChatShape'),
ExtHostChat: createProxyIdentifier<ExtHostChatShape>('ExtHostChat'),
ExtHostChatAgents: createProxyIdentifier<ExtHostChatAgentsShape>('ExtHostChatAgents'),
ExtHostChatAgents2: createProxyIdentifier<ExtHostChatAgentsShape2>('ExtHostChatAgents'),
ExtHostChatVariables: createProxyIdentifier<ExtHostChatVariablesShape>('ExtHostChatVariables'),
ExtHostChatProvider: createProxyIdentifier<ExtHostChatProviderShape>('ExtHostChatProvider'),

View file

@ -3,19 +3,15 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { raceCancellation } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Emitter } from 'vs/base/common/event';
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { Iterable } from 'vs/base/common/iterator';
import { toDisposable } from 'vs/base/common/lifecycle';
import { StopWatch } from 'vs/base/common/stopwatch';
import { localize } from 'vs/nls';
import { IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { ILogService } from 'vs/platform/log/common/log';
import { ExtHostChatShape, IChatRequestDto, IChatResponseDto, IChatDto, IMainContext, MainContext, MainThreadChatShape } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostChatShape, IChatDto, IMainContext, MainContext, MainThreadChatShape } from 'vs/workbench/api/common/extHost.protocol';
import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters';
import { IChatFollowup, IChatReplyFollowup, IChatUserActionEvent, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatReplyFollowup, IChatUserActionEvent } from 'vs/workbench/contrib/chat/common/chatService';
import type * as vscode from 'vscode';
class ChatProviderWrapper<T> {
@ -36,7 +32,6 @@ export class ExtHostChat implements ExtHostChatShape {
private readonly _chatProvider = new Map<number, ChatProviderWrapper<vscode.InteractiveSessionProvider>>();
private readonly _chatSessions = new Map<number, vscode.InteractiveSession>();
// private readonly _providerResponsesByRequestId = new Map<number, { response: vscode.ProviderResult<vscode.InteractiveResponse | vscode.InteractiveResponseForProgress>; sessionId: number }>();
private readonly _onDidPerformUserAction = new Emitter<vscode.InteractiveSessionUserActionEvent>();
public readonly onDidPerformUserAction = this._onDidPerformUserAction.event;
@ -45,7 +40,6 @@ export class ExtHostChat implements ExtHostChatShape {
constructor(
mainContext: IMainContext,
private readonly logService: ILogService
) {
this._proxy = mainContext.getProxy(MainContext.MainThreadChat);
}
@ -75,13 +69,13 @@ export class ExtHostChat implements ExtHostChatShape {
this._proxy.$sendRequestToProvider(providerId, message);
}
async $prepareChat(handle: number, initialState: any, token: CancellationToken): Promise<IChatDto | undefined> {
async $prepareChat(handle: number, token: CancellationToken): Promise<IChatDto | undefined> {
const entry = this._chatProvider.get(handle);
if (!entry) {
return undefined;
}
const session = await entry.provider.prepareSession(initialState, token);
const session = await entry.provider.prepareSession(token);
if (!session) {
return undefined;
}
@ -124,25 +118,6 @@ export class ExtHostChat implements ExtHostChatShape {
});
}
async $provideFollowups(handle: number, sessionId: number, token: CancellationToken): Promise<IChatFollowup[] | undefined> {
const entry = this._chatProvider.get(handle);
if (!entry) {
return undefined;
}
const realSession = this._chatSessions.get(sessionId);
if (!realSession) {
return;
}
if (!entry.provider.provideFollowups) {
return undefined;
}
const rawFollowups = await entry.provider.provideFollowups(realSession, token);
return rawFollowups?.map(f => typeConvert.ChatFollowup.from(f));
}
async $provideSampleQuestions(handle: number, token: CancellationToken): Promise<IChatReplyFollowup[] | undefined> {
const entry = this._chatProvider.get(handle);
if (!entry) {
@ -161,121 +136,6 @@ export class ExtHostChat implements ExtHostChatShape {
return rawFollowups?.map(f => typeConvert.ChatReplyFollowup.from(f));
}
$removeRequest(handle: number, sessionId: number, requestId: string): void {
const entry = this._chatProvider.get(handle);
if (!entry) {
return;
}
const realSession = this._chatSessions.get(sessionId);
if (!realSession) {
return;
}
if (!entry.provider.removeRequest) {
return;
}
entry.provider.removeRequest(realSession, requestId);
}
async $provideReply(handle: number, sessionId: number, request: IChatRequestDto, token: CancellationToken): Promise<IChatResponseDto | undefined> {
const entry = this._chatProvider.get(handle);
if (!entry) {
return undefined;
}
const realSession = this._chatSessions.get(sessionId);
if (!realSession) {
return;
}
const requestObj: vscode.InteractiveRequest = {
session: realSession,
message: request.message,
variables: {}
};
if (request.variables) {
for (const key of Object.keys(request.variables)) {
requestObj.variables[key] = request.variables[key].map(typeConvert.ChatVariable.to);
}
}
const stopWatch = StopWatch.create(false);
let firstProgress: number | undefined;
const progressObj: vscode.Progress<vscode.InteractiveProgress> = {
report: (progress: vscode.InteractiveProgress) => {
if (token.isCancellationRequested) {
return;
}
if (typeof firstProgress === 'undefined') {
firstProgress = stopWatch.elapsed();
}
const convertedProgress = typeConvert.ChatResponseProgress.from(entry.extension, progress);
if ('placeholder' in progress && 'resolvedContent' in progress) {
const resolvedContent = Promise.all([this._proxy.$acceptResponseProgress(handle, sessionId, convertedProgress), progress.resolvedContent]);
raceCancellation(resolvedContent, token).then((res) => {
if (!res) {
return; /* Cancelled */
}
const [progressHandle, progressContent] = res;
this._proxy.$acceptResponseProgress(handle, sessionId, progressContent, progressHandle ?? undefined);
});
} else {
this._proxy.$acceptResponseProgress(handle, sessionId, convertedProgress);
}
}
};
let result: vscode.InteractiveResponseForProgress | undefined | null;
try {
result = await entry.provider.provideResponseWithProgress(requestObj, progressObj, token);
if (!result) {
result = { errorDetails: { message: localize('emptyResponse', "Provider returned null response") } };
}
} catch (err) {
result = { errorDetails: { message: localize('errorResponse', "Error from provider: {0}", err.message), responseIsIncomplete: true } };
this.logService.error(err);
}
try {
// Check that the session has not been released since the request started
if (realSession.saveState && this._chatSessions.has(sessionId)) {
const newState = realSession.saveState();
this._proxy.$acceptChatState(sessionId, newState);
}
} catch (err) {
this.logService.warn(err);
}
const timings = { firstProgress: firstProgress ?? 0, totalElapsed: stopWatch.elapsed() };
return { errorDetails: result.errorDetails, timings };
}
async $provideSlashCommands(handle: number, sessionId: number, token: CancellationToken): Promise<ISlashCommand[] | undefined> {
const entry = this._chatProvider.get(handle);
if (!entry) {
return undefined;
}
const realSession = this._chatSessions.get(sessionId);
if (!realSession) {
return undefined;
}
if (!entry.provider.provideSlashCommands) {
return undefined;
}
const slashCommands = await entry.provider.provideSlashCommands(realSession, token);
return slashCommands?.map(c => (<ISlashCommand>{
...c,
kind: typeConvert.CompletionItemKind.from(c.kind)
}));
}
$releaseSession(sessionId: number) {
this._chatSessions.delete(sessionId);
}

View file

@ -1,91 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DeferredPromise, raceCancellation } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { ILogService } from 'vs/platform/log/common/log';
import { Progress } from 'vs/platform/progress/common/progress';
import { ExtHostChatAgentsShape, IMainContext, MainContext, MainThreadChatAgentsShape } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider';
import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters';
import { ChatMessageRole } from 'vs/workbench/api/common/extHostTypes';
import { IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider';
import type * as vscode from 'vscode';
export class ExtHostChatAgents implements ExtHostChatAgentsShape {
private static _idPool = 0;
private readonly _agents = new Map<number, { extension: ExtensionIdentifier; agent: vscode.ChatAgent }>();
private readonly _proxy: MainThreadChatAgentsShape;
constructor(
mainContext: IMainContext,
private readonly _extHostChatProvider: ExtHostChatProvider,
private readonly _logService: ILogService,
) {
this._proxy = mainContext.getProxy(MainContext.MainThreadChatAgents);
}
registerAgent(extension: ExtensionIdentifier, name: string, agent: vscode.ChatAgent, metadata: vscode.ChatAgentMetadata): IDisposable {
const handle = ExtHostChatAgents._idPool++;
this._agents.set(handle, { extension, agent });
this._proxy.$registerAgent(handle, name, metadata);
return toDisposable(() => {
this._proxy.$unregisterAgent(handle);
this._agents.delete(handle);
});
}
async $invokeAgent(handle: number, requestId: number, prompt: string, context: { history: IChatMessage[] }, token: CancellationToken): Promise<any> {
const data = this._agents.get(handle);
if (!data) {
this._logService.warn(`[CHAT](${handle}) CANNOT invoke agent because the agent is not registered`);
return;
}
let done = false;
function throwIfDone() {
if (done) {
throw new Error('Only valid while executing the command');
}
}
const commandExecution = new DeferredPromise<void>();
token.onCancellationRequested(() => commandExecution.complete());
setTimeout(() => commandExecution.complete(), 10 * 1000);
this._extHostChatProvider.allowListExtensionWhile(data.extension, commandExecution.p);
const task = data.agent(
{ role: ChatMessageRole.User, content: prompt },
{ history: context.history.map(typeConvert.ChatMessage.to) },
new Progress<vscode.ChatAgentResponse>(p => {
throwIfDone();
this._proxy.$handleProgressChunk(requestId, { content: isInteractiveProgressFileTree(p.message) ? p.message : p.message.value });
}),
token
);
try {
return await raceCancellation(Promise.resolve(task).then((v) => {
if (v && 'followUp' in v) {
const convertedFollowup = v?.followUp?.map(f => typeConvert.ChatFollowup.from(f));
return { followUp: convertedFollowup };
}
return undefined;
}), token);
} finally {
done = true;
commandExecution.complete();
}
}
}
function isInteractiveProgressFileTree(thing: unknown): thing is vscode.InteractiveProgressFileTree {
return !!thing && typeof thing === 'object' && 'treeData' in thing;
}

View file

@ -2157,20 +2157,10 @@ export namespace DataTransfer {
}
export namespace ChatReplyFollowup {
export function to(followup: IChatReplyFollowup): vscode.InteractiveSessionReplyFollowup {
return {
message: followup.message,
metadata: followup.metadata,
title: followup.title,
tooltip: followup.tooltip,
};
}
export function from(followup: vscode.InteractiveSessionReplyFollowup): IChatReplyFollowup {
return {
kind: 'reply',
message: followup.message,
metadata: followup.metadata,
title: followup.title,
tooltip: followup.tooltip,
};
@ -2178,7 +2168,7 @@ export namespace ChatReplyFollowup {
}
export namespace ChatFollowup {
export function from(followup: string | vscode.InteractiveSessionFollowup): IChatFollowup {
export function from(followup: string | vscode.ChatAgentFollowup): IChatFollowup {
if (typeof followup === 'string') {
return <IChatReplyFollowup>{ title: followup, message: followup, kind: 'reply' };
} else if ('commandId' in followup) {
@ -2303,11 +2293,9 @@ export namespace InteractiveEditorResponseFeedbackKind {
}
export namespace ChatResponseProgress {
export function from(extension: IExtensionDescription, progress: vscode.InteractiveProgress | vscode.ChatAgentExtendedProgress): extHostProtocol.IChatResponseProgressDto {
export function from(extension: IExtensionDescription, progress: vscode.ChatAgentExtendedProgress): extHostProtocol.IChatResponseProgressDto {
if ('placeholder' in progress && 'resolvedContent' in progress) {
return { placeholder: progress.placeholder };
} else if ('responseId' in progress) {
return { requestId: progress.responseId };
} else if ('markdownContent' in progress) {
checkProposedApiEnabled(extension, 'chatAgents2Additions');
return { content: MarkdownString.from(progress.markdownContent) };

View file

@ -28,7 +28,7 @@ import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatAct
import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
import { ICodeBlockActionContext } from 'vs/workbench/contrib/chat/browser/codeBlockPart';
import { CONTEXT_IN_CHAT_SESSION, CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { IChatCopyAction, IChatService, IDocumentContext, InteractiveSessionCopyKind } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatService, IDocumentContext, InteractiveSessionCopyKind } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatResponseViewModel, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
import { CTX_INLINE_CHAT_VISIBLE } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations';
@ -111,9 +111,8 @@ export function registerChatCodeBlockActions() {
agentId: context.element.agent?.id,
sessionId: context.element.sessionId,
requestId: context.element.requestId,
action: <IChatCopyAction>{
action: {
kind: 'copy',
responseId: context.element.providerResponseId,
codeBlockIndex: context.codeBlockIndex,
copyType: InteractiveSessionCopyKind.Toolbar,
copiedCharacters: context.code.length,
@ -149,24 +148,21 @@ export function registerChatCodeBlockActions() {
const totalCharacters = editorModel.getValueLength();
// Report copy to extensions
if (context.element.providerResponseId) {
const chatService = accessor.get(IChatService);
chatService.notifyUserAction({
providerId: context.element.providerId,
agentId: context.element.agent?.id,
sessionId: context.element.sessionId,
requestId: context.element.requestId,
action: {
kind: 'copy',
codeBlockIndex: context.codeBlockIndex,
responseId: context.element.providerResponseId,
copyType: InteractiveSessionCopyKind.Action,
copiedText,
copiedCharacters: copiedText.length,
totalCharacters,
}
});
}
const chatService = accessor.get(IChatService);
chatService.notifyUserAction({
providerId: context.element.providerId,
agentId: context.element.agent?.id,
sessionId: context.element.sessionId,
requestId: context.element.requestId,
action: {
kind: 'copy',
codeBlockIndex: context.codeBlockIndex,
copyType: InteractiveSessionCopyKind.Action,
copiedText,
copiedCharacters: copiedText.length,
totalCharacters,
}
});
// Copy full cell if no selection, otherwise fall back on normal editor implementation
if (noSelection) {
@ -337,7 +333,6 @@ export function registerChatCodeBlockActions() {
requestId: context.element.requestId,
action: {
kind: 'insert',
responseId: context.element.providerResponseId!,
codeBlockIndex: context.codeBlockIndex,
totalCharacters: context.code.length,
}
@ -386,7 +381,6 @@ export function registerChatCodeBlockActions() {
requestId: context.element.requestId,
action: {
kind: 'insert',
responseId: context.element.providerResponseId!,
codeBlockIndex: context.codeBlockIndex,
totalCharacters: context.code.length,
newFile: true
@ -482,7 +476,6 @@ export function registerChatCodeBlockActions() {
requestId: context.element.requestId,
action: {
kind: 'runInTerminal',
responseId: context.element.providerResponseId!,
codeBlockIndex: context.codeBlockIndex,
languageId: context.languageId,
}

View file

@ -60,7 +60,6 @@ export function registerChatTitleActions() {
action: {
kind: 'vote',
direction: InteractiveSessionVoteDirection.Up,
responseId: item.providerResponseId!,
}
});
item.setVote(InteractiveSessionVoteDirection.Up);
@ -103,7 +102,6 @@ export function registerChatTitleActions() {
action: {
kind: 'vote',
direction: InteractiveSessionVoteDirection.Down,
responseId: item.providerResponseId!,
}
});
item.setVote(InteractiveSessionVoteDirection.Down);
@ -222,12 +220,12 @@ export function registerChatTitleActions() {
item = widget?.getFocus();
}
const providerRequestId = isRequestVM(item) ? item.providerRequestId :
isResponseVM(item) ? item.providerResponseId : undefined;
const requestId = isRequestVM(item) ? item.id :
isResponseVM(item) ? item.requestId : undefined;
if (providerRequestId) {
if (requestId) {
const chatService = accessor.get(IChatService);
chatService.removeRequest(item.sessionId, providerRequestId);
chatService.removeRequest(item.sessionId, requestId);
}
}
});

View file

@ -3,14 +3,13 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatRequestViewModel, IChatResponseViewModel, IChatViewModel, IChatWelcomeMessageViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel';
import { Event } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { Selection } from 'vs/editor/common/core/selection';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IChatWidgetContrib } from 'vs/workbench/contrib/chat/browser/chatWidget';
import { Selection } from 'vs/editor/common/core/selection';
import { IChatRequestViewModel, IChatResponseViewModel, IChatViewModel, IChatWelcomeMessageViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel';
export const IChatWidgetService = createDecorator<IChatWidgetService>('chatWidgetService');
export const IQuickChatService = createDecorator<IQuickChatService>('quickChatService');
@ -121,7 +120,6 @@ export interface IChatWidget {
focusLastMessage(): void;
focusInput(): void;
hasInputFocus(): boolean;
getSlashCommands(): Promise<ISlashCommand[] | undefined>;
getCodeBlockInfoForEditor(uri: URI): IChatCodeBlockInfo | undefined;
getCodeBlockInfosForResponse(response: IChatResponseViewModel): IChatCodeBlockInfo[];
getFileTreeInfosForResponse(response: IChatResponseViewModel): IChatFileTreeInfo[];

View file

@ -54,10 +54,10 @@ import { convertParsedRequestToMarkdown, reduceInlineContentReferences, walkTree
import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions';
import { CodeBlockPart, ICodeBlockData, ICodeBlockPart } from 'vs/workbench/contrib/chat/browser/codeBlockPart';
import { IChatAgentMetadata } from 'vs/workbench/contrib/chat/common/chatAgents';
import { CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_HAS_PROVIDER_ID, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { IPlaceholderMarkdownString } from 'vs/workbench/contrib/chat/common/chatModel';
import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes';
import { IChatContentReference, IChatReplyFollowup, IChatResponseProgressFileTreeData, IChatService, ISlashCommand, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatContentReference, IChatReplyFollowup, IChatResponseProgressFileTreeData, IChatService, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatResponseMarkdownRenderData, IChatResponseRenderData, IChatResponseViewModel, IChatWelcomeMessageViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
import { IWordCountResult, getNWords } from 'vs/workbench/contrib/chat/common/chatWordCounter';
import { createFileIconThemableTreeContainerScope } from 'vs/workbench/contrib/files/browser/views/explorerView';
@ -89,7 +89,6 @@ const forceVerboseLayoutTracing = false;
export interface IChatRendererDelegate {
getListLength(): number;
getSlashCommands(): ISlashCommand[];
}
export interface IChatListItemRendererOptions {
@ -264,7 +263,6 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
CONTEXT_RESPONSE.bindTo(templateData.contextKeyService).set(isResponseVM(element));
CONTEXT_REQUEST.bindTo(templateData.contextKeyService).set(isRequestVM(element));
CONTEXT_RESPONSE_HAS_PROVIDER_ID.bindTo(templateData.contextKeyService).set(isResponseVM(element) && !!element.providerResponseId);
if (isResponseVM(element)) {
CONTEXT_RESPONSE_VOTE.bindTo(templateData.contextKeyService).set(element.vote === InteractiveSessionVoteDirection.Up ? 'up' : element.vote === InteractiveSessionVoteDirection.Down ? 'down' : '');
} else {
@ -465,7 +463,6 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
private renderWelcomeMessage(element: IChatWelcomeMessageViewModel, templateData: IChatListItemTemplate, height?: number) {
dom.clearNode(templateData.value);
dom.clearNode(templateData.referencesListContainer);
const slashCommands = this.delegate.getSlashCommands();
for (const item of element.content) {
if (Array.isArray(item)) {
@ -477,11 +474,6 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
templateData.contextKeyService));
} else {
const result = this.renderMarkdown(item as IMarkdownString, element, templateData);
for (const codeElement of result.element.querySelectorAll('code')) {
if (codeElement.textContent && slashCommands.find(command => codeElement.textContent === `/${command.command}`)) {
codeElement.classList.add('interactive-slash-command');
}
}
templateData.value.appendChild(result.element);
templateData.elementDisposables.add(result);
}
@ -779,11 +771,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
const disposables = new DisposableStore();
let codeBlockIndex = 0;
// TODO if the slash commands stay completely dynamic, this isn't quite right
const slashCommands = this.delegate.getSlashCommands();
const usedSlashCommand = slashCommands.find(s => markdown.value.startsWith(`/${s.command} `));
const toRender = usedSlashCommand ? markdown.value.slice(usedSlashCommand.command.length + 2) : markdown.value;
markdown = new MarkdownString(toRender, {
markdown = new MarkdownString(markdown.value, {
isTrusted: {
// Disable all other config options except isTrusted
enabledCommands: typeof markdown.isTrusted === 'object' ? markdown.isTrusted?.enabledCommands : [] ?? []
@ -831,15 +819,6 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
walkTreeAndAnnotateReferenceLinks(result.element);
if (usedSlashCommand) {
const slashCommandElement = $('span.interactive-slash-command', { title: usedSlashCommand.detail }, `/${usedSlashCommand.command} `);
if (result.element.firstChild?.nodeName.toLowerCase() === 'p') {
result.element.firstChild.insertBefore(slashCommandElement, result.element.firstChild.firstChild);
} else {
result.element.insertBefore($('p', undefined, slashCommandElement), result.element.firstChild);
}
}
orderedDisposablesList.reverse().forEach(d => disposables.add(d));
return {
element: result.element,

View file

@ -6,7 +6,6 @@
import * as dom from 'vs/base/browser/dom';
import { ITreeContextMenuEvent, ITreeElement } from 'vs/base/browser/ui/tree/tree';
import { disposableTimeout } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { Emitter } from 'vs/base/common/event';
import { Disposable, DisposableStore, IDisposable, MutableDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle';
@ -23,7 +22,7 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle
import { WorkbenchObjectTree } from 'vs/platform/list/browser/listService';
import { ILogService } from 'vs/platform/log/common/log';
import { IViewsService } from 'vs/workbench/common/views';
import { ChatTreeItem, IChatWidgetViewOptions, IChatAccessibilityService, IChatCodeBlockInfo, IChatFileTreeInfo, IChatWidget, IChatWidgetService, IChatWidgetViewContext } from 'vs/workbench/contrib/chat/browser/chat';
import { ChatTreeItem, IChatAccessibilityService, IChatCodeBlockInfo, IChatFileTreeInfo, IChatWidget, IChatWidgetService, IChatWidgetViewContext, IChatWidgetViewOptions } from 'vs/workbench/contrib/chat/browser/chat';
import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart';
import { ChatAccessibilityProvider, ChatListDelegate, ChatListItemRenderer, IChatListItemRendererOptions, IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer';
import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions';
@ -31,7 +30,7 @@ import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane';
import { CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService';
import { ChatModelInitState, IChatModel } from 'vs/workbench/contrib/chat/common/chatModel';
import { IChatReplyFollowup, IChatService, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatReplyFollowup, IChatService } from 'vs/workbench/contrib/chat/common/chatService';
import { ChatViewModel, IChatResponseViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
const $ = dom.$;
@ -109,15 +108,6 @@ export class ChatWidget extends Disposable implements IChatWidget {
this.viewModelDisposables.add(viewModel);
}
this.slashCommandsPromise = undefined;
this.lastSlashCommands = undefined;
this.getSlashCommands().then(() => {
if (!this._isDisposed) {
this.onDidChangeItems();
}
});
this._onDidChangeViewModel.fire();
}
@ -125,9 +115,6 @@ export class ChatWidget extends Disposable implements IChatWidget {
return this._viewModel;
}
private lastSlashCommands: ISlashCommand[] | undefined;
private slashCommandsPromise: Promise<ISlashCommand[] | undefined> | undefined;
constructor(
readonly viewContext: IChatWidgetViewContext,
private readonly viewOptions: IChatWidgetViewOptions,
@ -164,12 +151,6 @@ export class ChatWidget extends Disposable implements IChatWidget {
return this.inputPart.inputUri;
}
private _isDisposed: boolean = false;
public override dispose(): void {
this._isDisposed = true;
super.dispose();
}
render(parent: HTMLElement): void {
const viewId = 'viewId' in this.viewContext ? this.viewContext.viewId : undefined;
this.editorOptions = this._register(this.instantiationService.createInstance(ChatEditorOptions, viewId, this.styles.listForeground, this.styles.inputEditorBackground, this.styles.resultEditorBackground));
@ -260,7 +241,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
// TODO? We can give the welcome message a proper VM or get rid of the rest of the VMs
((isWelcomeVM(element) && this.viewModel) ? `_${ChatModelInitState[this.viewModel.initState]}` : '') +
// Ensure re-rendering an element once slash commands are loaded, so the colorization can be applied.
`${(isRequestVM(element) || isWelcomeVM(element)) && !!this.lastSlashCommands ? '_scLoaded' : ''}` +
`${(isRequestVM(element) || isWelcomeVM(element)) /* && !!this.lastSlashCommands ? '_scLoaded' : '' */}` +
// If a response is in the process of progressive rendering, we need to ensure that it will
// be re-rendered so progressive rendering is restarted, even if the model wasn't updated.
`${isResponseVM(element) && element.renderData ? `_${this.visibleChangeCount}` : ''}` +
@ -309,27 +290,11 @@ export class ChatWidget extends Disposable implements IChatWidget {
}
}
async getSlashCommands(): Promise<ISlashCommand[] | undefined> {
if (!this.viewModel) {
return;
}
if (!this.slashCommandsPromise) {
this.slashCommandsPromise = this.chatService.getSlashCommands(this.viewModel.sessionId, CancellationToken.None).then(commands => {
this.lastSlashCommands = commands ?? [];
return this.lastSlashCommands;
});
}
return this.slashCommandsPromise;
}
private createList(listContainer: HTMLElement, options: IChatListItemRendererOptions): void {
const scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.contextKeyService]));
const delegate = scopedInstantiationService.createInstance(ChatListDelegate);
const rendererDelegate: IChatRendererDelegate = {
getListLength: () => this.tree.getNode(null).visibleChildrenCount,
getSlashCommands: () => this.lastSlashCommands ?? [],
};
this.renderer = this._register(scopedInstantiationService.createInstance(
ChatListItemRenderer,
@ -462,7 +427,6 @@ export class ChatWidget extends Disposable implements IChatWidget {
this.container.setAttribute('data-session-id', model.sessionId);
this.viewModel = this.instantiationService.createInstance(ChatViewModel, model);
this.viewModelDisposables.add(this.viewModel.onDidChange(e => {
this.slashCommandsPromise = undefined;
this.requestInProgress.set(this.viewModel!.requestInProgress);
this.onDidChangeItems();
if (e?.kind === 'addRequest') {
@ -536,8 +500,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
'query' in opts ? opts.query :
`${opts.prefix} ${editorValue}`;
const isUserQuery = !opts || 'query' in opts;
const usedSlashCommand = this.lookupSlashCommand(input);
const result = await this.chatService.sendRequest(this.viewModel.sessionId, input, usedSlashCommand);
const result = await this.chatService.sendRequest(this.viewModel.sessionId, input);
if (result) {
this.inputPart.acceptInput(isUserQuery ? input : undefined);
@ -552,10 +515,6 @@ export class ChatWidget extends Disposable implements IChatWidget {
}
}
private lookupSlashCommand(input: string): ISlashCommand | undefined {
return this.lastSlashCommands?.find(sc => input.startsWith(`/${sc.command}`));
}
getCodeBlockInfosForResponse(response: IChatResponseViewModel): IChatCodeBlockInfo[] {
return this.renderer.getCodeBlockInfosForResponse(response);
}

View file

@ -32,6 +32,7 @@ import { chatSlashCommandBackground, chatSlashCommandForeground } from 'vs/workb
import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, ChatRequestTextPart, ChatRequestVariablePart, chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes';
import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser';
import { IChatService, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands';
import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables';
import { isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
@ -49,7 +50,7 @@ class InputEditorDecorations extends Disposable {
public readonly id = 'inputEditorDecorations';
private readonly previouslyUsedSlashCommands = new Set<string>();
private readonly previouslyUsedAgents = new Set<string>();
private readonly viewModelDisposables = this._register(new MutableDisposable());
@ -71,16 +72,12 @@ class InputEditorDecorations extends Disposable {
this._register(this.widget.inputEditor.onDidChangeModelContent(() => this.updateInputEditorDecorations()));
this._register(this.widget.onDidChangeViewModel(() => {
this.registerViewModelListeners();
this.previouslyUsedSlashCommands.clear();
this.previouslyUsedAgents.clear();
this.updateInputEditorDecorations();
}));
this._register(this.chatService.onDidSubmitSlashCommand((e) => {
this._register(this.chatService.onDidSubmitAgent((e) => {
if (e.sessionId === this.widget.viewModel?.sessionId) {
if ('agent' in e) {
this.previouslyUsedSlashCommands.add(agentAndCommandToKey(e.agent.id, e.slashCommand.name));
} else {
this.previouslyUsedSlashCommands.add(e.slashCommand);
}
this.previouslyUsedAgents.add(agentAndCommandToKey(e.agent.id, e.slashCommand.name));
}
}));
@ -187,7 +184,7 @@ class InputEditorDecorations extends Disposable {
const onlyAgentCommandAndWhitespace = agentPart && agentSubcommandPart && parsedRequest.every(p => p instanceof ChatRequestTextPart && !p.text.trim().length || p instanceof ChatRequestAgentPart || p instanceof ChatRequestAgentSubcommandPart);
if (onlyAgentCommandAndWhitespace) {
// Agent reference and subcommand with no other text - show the placeholder
const isFollowupSlashCommand = this.previouslyUsedSlashCommands.has(agentAndCommandToKey(agentPart.agent.id, agentSubcommandPart.command.name));
const isFollowupSlashCommand = this.previouslyUsedAgents.has(agentAndCommandToKey(agentPart.agent.id, agentSubcommandPart.command.name));
const shouldRenderFollowupPlaceholder = isFollowupSlashCommand && agentSubcommandPart.command.followupPlaceholder;
if (agentSubcommandPart?.command.description) {
placeholderDecoration = [{
@ -210,7 +207,7 @@ class InputEditorDecorations extends Disposable {
const onlySlashCommandAndWhitespace = slashCommandPart && parsedRequest.every(p => p instanceof ChatRequestTextPart && !p.text.trim().length || p instanceof ChatRequestSlashCommandPart);
if (onlySlashCommandAndWhitespace) {
// Command reference with no other text - show the placeholder
const isFollowupSlashCommand = this.previouslyUsedSlashCommands.has(slashCommandPart.slashCommand.command);
const isFollowupSlashCommand = this.previouslyUsedAgents.has(slashCommandPart.slashCommand.command);
const shouldRenderFollowupPlaceholder = isFollowupSlashCommand && slashCommandPart.slashCommand.followupPlaceholder;
if (shouldRenderFollowupPlaceholder || slashCommandPart.slashCommand.detail) {
placeholderDecoration = [{
@ -264,15 +261,12 @@ class InputEditorSlashCommandMode extends Disposable {
@IChatService private readonly chatService: IChatService
) {
super();
this._register(this.chatService.onDidSubmitSlashCommand(e => {
this._register(this.chatService.onDidSubmitAgent(e => {
if (this.widget.viewModel?.sessionId !== e.sessionId) {
return;
}
if ('agent' in e) {
this.repopulateAgentCommand(e.agent, e.slashCommand);
} else {
this.repopulateSlashCommand(e.slashCommand);
}
this.repopulateAgentCommand(e.agent, e.slashCommand);
}));
}
@ -283,20 +277,6 @@ class InputEditorSlashCommandMode extends Disposable {
this.widget.inputEditor.setPosition({ lineNumber: 1, column: value.length + 1 });
}
}
private async repopulateSlashCommand(slashCommand: string) {
const slashCommands = await this.widget.getSlashCommands();
if (this.widget.inputEditor.getValue().trim().length !== 0) {
return;
}
if (slashCommands?.find(c => c.command === slashCommand)?.shouldRepopulate) {
const value = `/${slashCommand} `;
this.widget.inputEditor.setValue(value);
this.widget.inputEditor.setPosition({ lineNumber: 1, column: value.length + 1 });
}
}
}
ChatWidget.CONTRIBS.push(InputEditorDecorations, InputEditorSlashCommandMode);
@ -306,11 +286,12 @@ class SlashCommandCompletions extends Disposable {
@ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService,
@IChatWidgetService private readonly chatWidgetService: IChatWidgetService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IChatSlashCommandService private readonly chatSlashCommandService: IChatSlashCommandService
) {
super();
this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, {
_debugDisplayName: 'chatSlashCommand',
_debugDisplayName: 'globalSlashCommands',
triggerCharacters: ['/'],
provideCompletionItems: async (model: ITextModel, _position: Position, _context: CompletionContext, _token: CancellationToken) => {
const widget = this.chatWidgetService.getWidgetByInputUri(model.uri);
@ -329,7 +310,7 @@ class SlashCommandCompletions extends Disposable {
return;
}
const slashCommands = await widget.getSlashCommands();
const slashCommands = this.chatSlashCommandService.getCommands();
if (!slashCommands) {
return null;
}

View file

@ -390,10 +390,6 @@
color: var(--vscode-notificationsInfoIcon-foreground) !important; /* Have to override default styles which apply to all lists */
}
.interactive-item-container .value .interactive-slash-command {
color: var(--vscode-textLink-foreground);
}
.interactive-session .interactive-input-part {
padding: 12px 0px;
display: flex;

View file

@ -6,7 +6,6 @@
import { localize } from 'vs/nls';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
export const CONTEXT_RESPONSE_HAS_PROVIDER_ID = new RawContextKey<boolean>('chatSessionResponseHasProviderId', false, { type: 'boolean', description: localize('interactiveSessionResponseHasProviderId', "True when the provider has assigned an id to this response.") });
export const CONTEXT_RESPONSE_VOTE = new RawContextKey<string>('chatSessionResponseVote', '', { type: 'string', description: localize('interactiveSessionResponseVote', "When the response has been voted up, is set to 'up'. When voted down, is set to 'down'. Otherwise an empty string.") });
export const CONTEXT_RESPONSE_FILTERED = new RawContextKey<boolean>('chatSessionResponseFiltered', false, { type: 'boolean', description: localize('chatResponseFiltered', "True when the chat response was filtered out by the server.") });
export const CONTEXT_CHAT_REQUEST_IN_PROGRESS = new RawContextKey<boolean>('chatSessionRequestInProgress', false, { type: 'boolean', description: localize('interactiveSessionRequestInProgress', "True when the current request is still in progress.") });

View file

@ -20,7 +20,6 @@ import { IChat, IChatContentInlineReference, IChatContentReference, IChatFollowu
export interface IChatRequestModel {
readonly id: string;
readonly providerRequestId: string | undefined;
readonly username: string;
readonly avatarIconUri?: URI;
readonly session: IChatModel;
@ -53,7 +52,6 @@ export interface IChatResponseModel {
readonly onDidChange: Event<void>;
readonly id: string;
readonly providerId: string;
readonly providerResponseId: string | undefined;
readonly requestId: string;
readonly username: string;
readonly avatarIconUri?: URI;
@ -79,10 +77,6 @@ export class ChatRequestModel implements IChatRequestModel {
return this._id;
}
public get providerRequestId(): string | undefined {
return this._providerRequestId;
}
public get username(): string {
return this.session.requesterUsername;
}
@ -93,14 +87,9 @@ export class ChatRequestModel implements IChatRequestModel {
constructor(
public readonly session: ChatModel,
public readonly message: IParsedChatRequest,
private _providerRequestId?: string) {
public readonly message: IParsedChatRequest) {
this._id = 'request_' + ChatRequestModel.nextId++;
}
setProviderRequestId(providerRequestId: string) {
this._providerRequestId = providerRequestId;
}
}
export interface IPlaceholderMarkdownString extends IMarkdownString {
@ -256,10 +245,6 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel
return this._id;
}
public get providerResponseId(): string | undefined {
return this._providerResponseId;
}
public get isComplete(): boolean {
return this._isComplete;
}
@ -317,7 +302,6 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel
private _isComplete: boolean = false,
private _isCanceled = false,
private _vote?: InteractiveSessionVoteDirection,
private _providerResponseId?: string,
private _errorDetails?: IChatResponseErrorDetails,
followups?: ReadonlyArray<IChatFollowup>
) {
@ -339,10 +323,6 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel
this._onDidChange.fire();
}
setProviderResponseId(providerResponseId: string) {
this._providerResponseId = providerResponseId;
}
setErrorDetails(errorDetails?: IChatResponseErrorDetails): void {
this._errorDetails = errorDetails;
this._onDidChange.fire();
@ -392,7 +372,6 @@ export interface ISerializableChatsData {
export type ISerializableChatAgentData = UriDto<IChatAgentData>;
export interface ISerializableChatRequestData {
providerRequestId: string | undefined;
message: string | IParsedChatRequest;
response: ReadonlyArray<IMarkdownString | IChatResponseProgressFileTreeData | IChatContentInlineReference> | undefined;
agent?: ISerializableChatAgentData;
@ -414,7 +393,6 @@ export interface IExportableChatData {
responderUsername: string;
requesterAvatarIconUri: UriComponents | undefined;
responderAvatarIconUri: UriComponents | undefined;
providerState: any;
}
export interface ISerializableChatData extends IExportableChatData {
@ -490,11 +468,6 @@ export class ChatModel extends Disposable implements IChatModel {
return this._welcomeMessage;
}
private _providerState: any;
get providerState(): any {
return this._providerState;
}
// TODO to be clear, this is not the same as the id from the session object, which belongs to the provider.
// It's easier to be able to identify this model before its async initialization is complete
private _sessionId: string;
@ -560,7 +533,6 @@ export class ChatModel extends Disposable implements IChatModel {
this._isImported = (!!initialData && !isSerializableSessionData(initialData)) || (initialData?.isImported ?? false);
this._sessionId = (isSerializableSessionData(initialData) && initialData.sessionId) || generateUuid();
this._requests = initialData ? this._deserialize(initialData) : [];
this._providerState = initialData ? initialData.providerState : undefined;
this._creationDate = (isSerializableSessionData(initialData) && initialData.creationDate) || Date.now();
this._initialRequesterAvatarIconUri = initialData?.requesterAvatarIconUri && URI.revive(initialData.requesterAvatarIconUri);
@ -585,11 +557,11 @@ export class ChatModel extends Disposable implements IChatModel {
typeof raw.message === 'string'
? this.getParsedRequestFromString(raw.message)
: reviveParsedChatRequest(raw.message);
const request = new ChatRequestModel(this, parsedRequest, raw.providerRequestId);
const request = new ChatRequestModel(this, parsedRequest);
if (raw.response || raw.responseErrorDetails) {
const agent = (raw.agent && 'metadata' in raw.agent) ? // Check for the new format, ignore entries in the old format
revive<ISerializableChatAgentData>(raw.agent) : undefined;
request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, agent, request.id, true, raw.isCanceled, raw.vote, raw.providerRequestId, raw.responseErrorDetails, raw.followups);
request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, agent, request.id, true, raw.isCanceled, raw.vote, raw.responseErrorDetails, raw.followups);
if (raw.usedContext) { // @ulugbekna: if this's a new vscode sessions, doc versions are incorrect anyway?
request.response.updateContent(raw.usedContext);
}
@ -642,13 +614,6 @@ export class ChatModel extends Disposable implements IChatModel {
}
this._isInitializedDeferred.complete();
if (session.onDidChangeState) {
this._register(session.onDidChangeState(state => {
this._providerState = state;
this.logService.trace('ChatModel#acceptNewSessionState');
}));
}
this._onDidChange.fire({ kind: 'initialize' });
}
@ -707,21 +672,15 @@ export class ChatModel extends Disposable implements IChatModel {
if (agent) {
request.response.setAgent(agent, progress.command);
}
} else {
request.setProviderRequestId(progress.requestId);
request.response.setProviderResponseId(progress.requestId);
}
}
removeRequest(requestId: string): void {
const index = this._requests.findIndex(request => request.providerRequestId === requestId);
removeRequest(id: string): void {
const index = this._requests.findIndex(request => request.id === id);
const request = this._requests[index];
if (!request.providerRequestId) {
return;
}
if (index !== -1) {
this._onDidChange.fire({ kind: 'removeRequest', requestId: request.providerRequestId, responseId: request.response?.providerResponseId });
this._onDidChange.fire({ kind: 'removeRequest', requestId: request.id, responseId: request.response?.id });
this._requests.splice(index, 1);
request.response?.dispose();
}
@ -782,21 +741,19 @@ export class ChatModel extends Disposable implements IChatModel {
}),
requests: this._requests.map((r): ISerializableChatRequestData => {
return {
providerRequestId: r.providerRequestId,
message: r.message,
response: r.response ? r.response.response.value : undefined,
responseErrorDetails: r.response?.errorDetails,
followups: r.response?.followups,
isCanceled: r.response?.isCanceled,
vote: r.response?.vote,
agent: r.response?.agent,
agent: r.response?.agent ? { id: r.response.agent.id, metadata: r.response.agent.metadata } : undefined, // May actually be the full IChatAgent instance, just take the data props
slashCommand: r.response?.slashCommand,
usedContext: r.response?.response.usedContext,
contentReferences: r.response?.response.contentReferences
};
}),
providerId: this.providerId,
providerState: this._providerState
};
}

View file

@ -9,7 +9,7 @@ import { IPosition, Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestDynamicReferencePart, ChatRequestSlashCommandPart, ChatRequestTextPart, ChatRequestVariablePart, IParsedChatRequest, IParsedChatRequestPart, chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes';
import { IChatService } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands';
import { IChatVariablesService, IDynamicReference } from 'vs/workbench/contrib/chat/common/chatVariables';
const agentReg = /^@([\w_\-]+)(?=(\s|$|\b))/i; // An @-agent
@ -21,7 +21,7 @@ export class ChatRequestParser {
constructor(
@IChatAgentService private readonly agentService: IChatAgentService,
@IChatVariablesService private readonly variableService: IChatVariablesService,
@IChatService private readonly chatService: IChatService,
@IChatSlashCommandService private readonly slashCommandService: IChatSlashCommandService
) { }
async parseChatRequest(sessionId: string, message: string): Promise<IParsedChatRequest> {
@ -174,7 +174,7 @@ export class ChatRequestParser {
return new ChatRequestAgentSubcommandPart(slashRange, slashEditorRange, subCommand);
}
} else {
const slashCommands = await this.chatService.getSlashCommands(sessionId, CancellationToken.None);
const slashCommands = this.slashCommandService.getCommands();
const slashCommand = slashCommands.find(c => c.command === command);
if (slashCommand) {
// Valid standalone slash command

View file

@ -23,7 +23,6 @@ export interface IChat {
responderUsername: string;
responderAvatarIconUri?: URI;
inputPlaceholder?: string;
onDidChangeState?: Event<any>;
dispose?(): void;
}
@ -100,7 +99,6 @@ export interface IChatAgentDetection {
export type IChatProgress =
| { content: string | IMarkdownString }
| { requestId: string }
| { treeData: IChatResponseProgressFileTreeData }
| { placeholder: string; resolvedContent: Promise<string | IMarkdownString | { treeData: IChatResponseProgressFileTreeData }> }
| IUsedContext
@ -108,18 +106,13 @@ export type IChatProgress =
| IChatContentInlineReference
| IChatAgentDetection;
export interface IPersistedChatState { }
export interface IChatProvider {
readonly id: string;
readonly displayName: string;
readonly iconUrl?: string;
prepareSession(initialState: IPersistedChatState | undefined, token: CancellationToken): ProviderResult<IChat | undefined>;
prepareSession(token: CancellationToken): ProviderResult<IChat | undefined>;
provideWelcomeMessage?(token: CancellationToken): ProviderResult<(string | IMarkdownString | IChatReplyFollowup[])[] | undefined>;
provideSampleQuestions?(token: CancellationToken): ProviderResult<IChatReplyFollowup[] | undefined>;
provideFollowups?(session: IChat, token: CancellationToken): ProviderResult<IChatFollowup[] | undefined>;
provideReply(request: IChatRequest, progress: (progress: IChatProgress) => void, token: CancellationToken): ProviderResult<IChatResponse>;
provideSlashCommands?(session: IChat, token: CancellationToken): ProviderResult<ISlashCommand[]>;
removeRequest?(session: IChat, requestId: string): void;
}
export interface ISlashCommand {
@ -156,7 +149,6 @@ export interface IChatReplyFollowup {
message: string;
title?: string;
tooltip?: string;
metadata?: any;
}
export interface IChatResponseCommandFollowup {
@ -177,7 +169,6 @@ export enum InteractiveSessionVoteDirection {
export interface IChatVoteAction {
kind: 'vote';
responseId: string;
direction: InteractiveSessionVoteDirection;
}
@ -189,7 +180,6 @@ export enum InteractiveSessionCopyKind {
export interface IChatCopyAction {
kind: 'copy';
responseId: string;
codeBlockIndex: number;
copyType: InteractiveSessionCopyKind;
copiedCharacters: number;
@ -199,7 +189,6 @@ export interface IChatCopyAction {
export interface IChatInsertAction {
kind: 'insert';
responseId: string;
codeBlockIndex: number;
totalCharacters: number;
newFile?: boolean;
@ -207,7 +196,6 @@ export interface IChatInsertAction {
export interface IChatTerminalAction {
kind: 'runInTerminal';
responseId: string;
codeBlockIndex: number;
languageId?: string;
}
@ -271,7 +259,7 @@ export interface IChatService {
_serviceBrand: undefined;
transferredSessionData: IChatTransferredSessionData | undefined;
onDidSubmitSlashCommand: Event<{ slashCommand: string; sessionId: string } | { agent: IChatAgentData; slashCommand: IChatAgentCommand; sessionId: string }>;
onDidSubmitAgent: Event<{ agent: IChatAgentData; slashCommand: IChatAgentCommand; sessionId: string }>;
onDidRegisterProvider: Event<{ providerId: string }>;
registerProvider(provider: IChatProvider): IDisposable;
hasSessions(providerId: string): boolean;
@ -285,10 +273,9 @@ export interface IChatService {
/**
* Returns whether the request was accepted.
*/
sendRequest(sessionId: string, message: string, usedSlashCommand?: ISlashCommand): Promise<{ responseCompletePromise: Promise<void> } | undefined>;
sendRequest(sessionId: string, message: string): Promise<{ responseCompletePromise: Promise<void> } | undefined>;
removeRequest(sessionid: string, requestId: string): Promise<void>;
cancelCurrentRequestForSession(sessionId: string): void;
getSlashCommands(sessionId: string, token: CancellationToken): Promise<ISlashCommand[]>;
clearSession(sessionId: string): void;
addCompleteRequest(sessionId: string, message: IParsedChatRequest | string, response: IChatCompleteResponse): void;
sendRequestToProvider(sessionId: string, message: IChatDynamicRequest): void;

View file

@ -8,7 +8,7 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { Emitter, Event } from 'vs/base/common/event';
import { MarkdownString, isMarkdownString } from 'vs/base/common/htmlContent';
import { Iterable } from 'vs/base/common/iterator';
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { Disposable, DisposableMap, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { revive } from 'vs/base/common/marshalling';
import { StopWatch } from 'vs/base/common/stopwatch';
import { URI, UriComponents } from 'vs/base/common/uri';
@ -27,7 +27,7 @@ import { ChatModel, ChatModelInitState, ChatRequestModel, ChatWelcomeMessageMode
import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart } from 'vs/workbench/contrib/chat/common/chatParserTypes';
import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider';
import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser';
import { IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatRequest, IChatResponse, IChatService, IChatTransferredSessionData, IChatUserActionEvent, ISlashCommand, InteractiveSessionCopyKind, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
import { IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatResponse, IChatService, IChatTransferredSessionData, IChatUserActionEvent, InteractiveSessionCopyKind, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands';
import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
@ -134,7 +134,7 @@ export class ChatService extends Disposable implements IChatService {
private readonly _providers = new Map<string, IChatProvider>();
private readonly _sessionModels = new Map<string, ChatModel>();
private readonly _sessionModels = this._register(new DisposableMap<string, ChatModel>());
private readonly _pendingRequests = new Map<string, CancelablePromise<void>>();
private readonly _persistedSessions: ISerializableChatsData;
private readonly _hasProvider: IContextKey<boolean>;
@ -147,8 +147,8 @@ export class ChatService extends Disposable implements IChatService {
private readonly _onDidPerformUserAction = this._register(new Emitter<IChatUserActionEvent>());
public readonly onDidPerformUserAction: Event<IChatUserActionEvent> = this._onDidPerformUserAction.event;
private readonly _onDidSubmitSlashCommand = this._register(new Emitter<{ slashCommand: string; sessionId: string } | { agent: IChatAgentData; slashCommand: IChatAgentCommand; sessionId: string }>());
public readonly onDidSubmitSlashCommand = this._onDidSubmitSlashCommand.event;
private readonly _onDidSubmitAgent = this._register(new Emitter<{ agent: IChatAgentData; slashCommand: IChatAgentCommand; sessionId: string }>());
public readonly onDidSubmitAgent = this._onDidSubmitAgent.event;
private readonly _onDidDisposeSession = this._register(new Emitter<{ sessionId: string; providerId: string; reason: 'initializationFailed' | 'cleared' }>());
public readonly onDidDisposeSession = this._onDidDisposeSession.event;
@ -365,7 +365,7 @@ export class ChatService extends Disposable implements IChatService {
let session: IChat | undefined;
try {
session = await provider.prepareSession(model.providerState, token) ?? undefined;
session = await provider.prepareSession(token) ?? undefined;
} catch (err) {
this.trace('initializeSession', `Provider initializeSession threw: ${err}`);
}
@ -387,8 +387,7 @@ export class ChatService extends Disposable implements IChatService {
} catch (err) {
this.trace('startSession', `initializeSession failed: ${err}`);
model.setInitializationError(err);
model.dispose();
this._sessionModels.delete(model.sessionId);
this._sessionModels.deleteAndDispose(model.sessionId);
this._onDidDisposeSession.fire({ sessionId: model.sessionId, providerId: model.providerId, reason: 'initializationFailed' });
}
}
@ -424,7 +423,7 @@ export class ChatService extends Disposable implements IChatService {
return this._startSession(data.providerId, data, CancellationToken.None);
}
async sendRequest(sessionId: string, request: string, usedSlashCommand?: ISlashCommand): Promise<{ responseCompletePromise: Promise<void> } | undefined> {
async sendRequest(sessionId: string, request: string): Promise<{ responseCompletePromise: Promise<void> } | undefined> {
this.trace('sendRequest', `sessionId: ${sessionId}, message: ${request.substring(0, 20)}${request.length > 20 ? '[...]' : ''}}`);
if (!request.trim()) {
this.trace('sendRequest', 'Rejected empty message');
@ -448,10 +447,10 @@ export class ChatService extends Disposable implements IChatService {
}
// This method is only returning whether the request was accepted - don't block on the actual request
return { responseCompletePromise: this._sendRequestAsync(model, sessionId, provider, request, usedSlashCommand) };
return { responseCompletePromise: this._sendRequestAsync(model, sessionId, provider, request) };
}
private async _sendRequestAsync(model: ChatModel, sessionId: string, provider: IChatProvider, message: string, usedSlashCommand?: ISlashCommand): Promise<void> {
private async _sendRequestAsync(model: ChatModel, sessionId: string, provider: IChatProvider, message: string): Promise<void> {
const parsedRequest = await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(sessionId, message);
let request: ChatRequestModel;
@ -486,7 +485,7 @@ export class ChatService extends Disposable implements IChatService {
} else if ('agentName' in progress) {
this.trace('sendRequest', `Provider returned an agent detection for session ${model.sessionId}:\n ${JSON.stringify(progress, null, '\t')}`);
} else {
this.trace('sendRequest', `Provider returned id for session ${model.sessionId}, ${progress.requestId}`);
this.trace('sendRequest', `Provider returned unknown progress for session ${model.sessionId}:\n ${JSON.stringify(progress, null, '\t')}`);
}
model.acceptResponseProgress(request, progress);
@ -503,7 +502,7 @@ export class ChatService extends Disposable implements IChatService {
result: 'cancelled',
requestType,
agent: agentPart?.agent.id ?? '',
slashCommand: agentSlashCommandPart ? agentSlashCommandPart.command.name : usedSlashCommand?.command,
slashCommand: agentSlashCommandPart ? agentSlashCommandPart.command.name : commandPart?.slashCommand.command,
chatSessionId: model.sessionId
});
@ -511,10 +510,8 @@ export class ChatService extends Disposable implements IChatService {
});
try {
if (usedSlashCommand?.command) {
this._onDidSubmitSlashCommand.fire({ slashCommand: usedSlashCommand.command, sessionId: model.sessionId });
} else if (agentPart && agentSlashCommandPart?.command) {
this._onDidSubmitSlashCommand.fire({ agent: agentPart.agent, slashCommand: agentSlashCommandPart.command, sessionId: model.sessionId });
if (agentPart && agentSlashCommandPart?.command) {
this._onDidSubmitAgent.fire({ agent: agentPart.agent, slashCommand: agentSlashCommandPart.command, sessionId: model.sessionId });
}
let rawResponse: IChatResponse | null | undefined;
@ -574,19 +571,7 @@ export class ChatService extends Disposable implements IChatService {
rawResponse = { session: model.session! };
} else {
request = model.addRequest(parsedRequest);
const requestProps: IChatRequest = {
session: model.session!,
message,
variables: {}
};
if ('parts' in parsedRequest) {
const varResult = await this.chatVariablesService.resolveVariables(parsedRequest, model, token);
requestProps.variables = varResult.variables;
requestProps.message = varResult.prompt;
}
rawResponse = await provider.provideReply(requestProps, progressCallback, token);
throw new Error(`Can't handle request`);
}
if (token.isCancellationRequested) {
@ -608,7 +593,7 @@ export class ChatService extends Disposable implements IChatService {
result,
requestType,
agent: agentPart?.agent.id ?? '',
slashCommand: agentSlashCommandPart ? agentSlashCommandPart.command.name : usedSlashCommand?.command,
slashCommand: agentSlashCommandPart ? agentSlashCommandPart.command.name : commandPart?.slashCommand.command,
chatSessionId: model.sessionId
});
model.setResponse(request, rawResponse);
@ -620,11 +605,6 @@ export class ChatService extends Disposable implements IChatService {
model.setFollowups(request, followups);
model.completeResponse(request);
});
} else if (provider.provideFollowups) {
Promise.resolve(provider.provideFollowups(model.session!, CancellationToken.None)).then(providerFollowups => {
model.setFollowups(request, providerFollowups ?? undefined);
model.completeResponse(request);
});
} else {
model.completeResponse(request);
}
@ -653,43 +633,6 @@ export class ChatService extends Disposable implements IChatService {
}
model.removeRequest(requestId);
provider.removeRequest?.(model.session!, requestId);
}
async getSlashCommands(sessionId: string, token: CancellationToken): Promise<ISlashCommand[]> {
const model = this._sessionModels.get(sessionId);
if (!model) {
throw new Error(`Unknown session: ${sessionId}`);
}
await model.waitForInitialization();
const provider = this._providers.get(model.providerId);
if (!provider) {
throw new Error(`Unknown provider: ${model.providerId}`);
}
const serviceResults = this.chatSlashCommandService.getCommands().map(data => {
return <ISlashCommand>{
command: data.command,
detail: data.detail,
sortText: data.sortText,
executeImmediately: data.executeImmediately
};
});
const mainProviderRequest = provider.provideSlashCommands?.(model.session!, token);
try {
const providerResults = await mainProviderRequest;
if (providerResults) {
return providerResults.concat(serviceResults);
}
return serviceResults;
} catch (e) {
this.logService.error(e);
return serviceResults;
}
}
async sendRequestToProvider(sessionId: string, message: IChatDynamicRequest): Promise<{ responseCompletePromise: Promise<void> } | undefined> {
@ -746,8 +689,7 @@ export class ChatService extends Disposable implements IChatService {
this._persistedSessions[sessionId] = model.toJSON();
model.dispose();
this._sessionModels.delete(sessionId);
this._sessionModels.deleteAndDispose(sessionId);
this._pendingRequests.get(sessionId)?.cancel();
this._onDidDisposeSession.fire({ sessionId, providerId: model.providerId, reason: 'cleared' });
}

View file

@ -55,7 +55,6 @@ export interface IChatViewModel {
export interface IChatRequestViewModel {
readonly id: string;
readonly providerRequestId: string | undefined;
readonly sessionId: string;
/** This ID updates every time the underlying data changes */
readonly dataId: string;
@ -88,7 +87,6 @@ export interface IChatResponseViewModel {
/** This ID updates every time the underlying data changes */
readonly dataId: string;
readonly providerId: string;
readonly providerResponseId: string | undefined;
/** The ID of the associated IChatRequestViewModel */
readonly requestId: string;
readonly username: string;
@ -173,12 +171,12 @@ export class ChatViewModel extends Disposable implements IChatViewModel {
} else if (e.kind === 'addResponse') {
this.onAddResponse(e.response);
} else if (e.kind === 'removeRequest') {
const requestIdx = this._items.findIndex(item => isRequestVM(item) && item.providerRequestId === e.requestId);
const requestIdx = this._items.findIndex(item => isRequestVM(item) && item.id === e.requestId);
if (requestIdx >= 0) {
this._items.splice(requestIdx, 1);
}
const responseIdx = e.responseId && this._items.findIndex(item => isResponseVM(item) && item.providerResponseId === e.responseId);
const responseIdx = e.responseId && this._items.findIndex(item => isResponseVM(item) && item.id === e.responseId);
if (typeof responseIdx === 'number' && responseIdx >= 0) {
const items = this._items.splice(responseIdx, 1);
const item = items[0];
@ -218,10 +216,6 @@ export class ChatRequestViewModel implements IChatRequestViewModel {
return this._model.id;
}
get providerRequestId() {
return this._model.providerRequestId;
}
get dataId() {
return this.id + `_${ChatModelInitState[this._model.session.initState]}`;
}
@ -269,10 +263,6 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi
return this._model.providerId;
}
get providerResponseId() {
return this._model.providerResponseId;
}
get sessionId() {
return this._model.session.sessionId;
}

View file

@ -6,32 +6,53 @@
welcomeMessage: undefined,
requests: [
{
providerRequestId: undefined,
message: {
text: "test request",
text: "@ChatProviderWithUsedContext test request",
parts: [
{
range: {
start: 0,
endExclusive: 12
endExclusive: 28
},
editorRange: {
startLineNumber: 1,
startColumn: 1,
endLineNumber: 1,
endColumn: 13
endColumn: 29
},
text: "test request",
agent: {
id: "ChatProviderWithUsedContext",
metadata: { },
provideSlashCommands: [Function provideSlashCommands],
invoke: [Function invoke]
},
kind: "agent"
},
{
range: {
start: 28,
endExclusive: 41
},
editorRange: {
startLineNumber: 1,
startColumn: 29,
endLineNumber: 1,
endColumn: 42
},
text: " test request",
kind: "text"
}
]
},
response: [ ],
responseErrorDetails: undefined,
followups: undefined,
followups: [ ],
isCanceled: false,
vote: undefined,
agent: undefined,
agent: {
id: "ChatProviderWithUsedContext",
metadata: { }
},
slashCommand: undefined,
usedContext: { documents: [
{
@ -58,6 +79,5 @@
contentReferences: [ ]
}
],
providerId: "ChatProviderWithUsedContext",
providerState: undefined
providerId: "testProvider"
}

View file

@ -5,6 +5,5 @@
responderAvatarIconUri: undefined,
welcomeMessage: undefined,
requests: [ ],
providerId: "ChatProviderWithUsedContext",
providerState: undefined
providerId: "testProvider"
}

View file

@ -6,32 +6,53 @@
welcomeMessage: undefined,
requests: [
{
providerRequestId: undefined,
message: {
parts: [
{
range: {
start: 0,
endExclusive: 12
endExclusive: 28
},
editorRange: {
startLineNumber: 1,
startColumn: 1,
endLineNumber: 1,
endColumn: 13
endColumn: 29
},
text: "test request",
agent: {
id: "ChatProviderWithUsedContext",
metadata: { },
provideSlashCommands: [Function provideSlashCommands],
invoke: [Function invoke]
},
kind: "agent"
},
{
range: {
start: 28,
endExclusive: 41
},
editorRange: {
startLineNumber: 1,
startColumn: 29,
endLineNumber: 1,
endColumn: 42
},
text: " test request",
kind: "text"
}
],
text: "test request"
text: "@ChatProviderWithUsedContext test request"
},
response: [ ],
responseErrorDetails: undefined,
followups: undefined,
followups: [ ],
isCanceled: false,
vote: undefined,
agent: undefined,
agent: {
id: "ChatProviderWithUsedContext",
metadata: { }
},
slashCommand: undefined,
usedContext: { documents: [
{
@ -58,6 +79,5 @@
contentReferences: [ ]
}
],
providerId: "ChatProviderWithUsedContext",
providerState: undefined
providerId: "testProvider"
}

View file

@ -6,11 +6,14 @@
import * as assert from 'assert';
import { timeout } from 'vs/base/common/async';
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
import { Range } from 'vs/editor/common/core/range';
import { OffsetRange } from 'vs/editor/common/core/offsetRange';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { ILogService, NullLogService } from 'vs/platform/log/common/log';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { ChatModel } from 'vs/workbench/contrib/chat/common/chatModel';
import { ChatRequestTextPart } from 'vs/workbench/contrib/chat/common/chatParserTypes';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
@ -98,4 +101,18 @@ suite('ChatModel', () => {
assert.throws(() => model.initialize({} as any, undefined));
});
test('removeRequest', async () => {
const model = testDisposables.add(instantiationService.createInstance(ChatModel, 'provider', undefined));
model.startInitialize();
model.initialize({} as any, undefined);
const text = 'hello';
model.addRequest({ text, parts: [new ChatRequestTextPart(new OffsetRange(0, text.length), new Range(1, text.length, 1, text.length), text)] });
const requests = model.getRequests();
assert.strictEqual(requests.length, 1);
model.removeRequest(requests[0].id);
assert.strictEqual(model.getRequests().length, 0);
});
});

View file

@ -11,7 +11,7 @@ import { ILogService, NullLogService } from 'vs/platform/log/common/log';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { ChatAgentService, IChatAgent, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser';
import { IChatService } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands';
import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
@ -48,9 +48,9 @@ suite('ChatRequestParser', () => {
});
test('slash command', async () => {
const chatService = mockObject<IChatService>()({});
chatService.getSlashCommands.returns(Promise.resolve([{ command: 'fix' }]));
instantiationService.stub(IChatService, chatService as any);
const slashCommandService = mockObject<IChatSlashCommandService>()({});
slashCommandService.getCommands.returns([{ command: 'fix' }]);
instantiationService.stub(IChatSlashCommandService, slashCommandService as any);
parser = instantiationService.createInstance(ChatRequestParser);
const text = '/fix this';
@ -59,9 +59,9 @@ suite('ChatRequestParser', () => {
});
test('invalid slash command', async () => {
const chatService = mockObject<IChatService>()({});
chatService.getSlashCommands.returns(Promise.resolve([{ command: 'fix' }]));
instantiationService.stub(IChatService, chatService as any);
const slashCommandService = mockObject<IChatSlashCommandService>()({});
slashCommandService.getCommands.returns([{ command: 'fix' }]);
instantiationService.stub(IChatSlashCommandService, slashCommandService as any);
parser = instantiationService.createInstance(ChatRequestParser);
const text = '/explain this';
@ -70,9 +70,9 @@ suite('ChatRequestParser', () => {
});
test('multiple slash commands', async () => {
const chatService = mockObject<IChatService>()({});
chatService.getSlashCommands.returns(Promise.resolve([{ command: 'fix' }]));
instantiationService.stub(IChatService, chatService as any);
const slashCommandService = mockObject<IChatSlashCommandService>()({});
slashCommandService.getCommands.returns([{ command: 'fix' }]);
instantiationService.stub(IChatSlashCommandService, slashCommandService as any);
parser = instantiationService.createInstance(ChatRequestParser);
const text = '/fix /fix';

View file

@ -5,7 +5,6 @@
import * as assert from 'assert';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { assertSnapshot } from 'vs/base/test/common/snapshot';
@ -22,10 +21,10 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IViewsService } from 'vs/workbench/common/views';
import { ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { ChatAgentService, IChatAgent, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService';
import { ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel';
import { IChat, IChatProgress, IChatProvider, IChatRequest, IChatResponse, IPersistedChatState, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService';
import { IChat, IChatProgress, IChatProvider, IChatRequest } from 'vs/workbench/contrib/chat/common/chatService';
import { ChatService } from 'vs/workbench/contrib/chat/common/chatServiceImpl';
import { ChatSlashCommandService, IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands';
import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables';
@ -36,29 +35,18 @@ import { TestContextService, TestExtensionService, TestStorageService } from 'vs
class SimpleTestProvider extends Disposable implements IChatProvider {
private static sessionId = 0;
lastInitialState = undefined;
readonly displayName = 'Test';
private _onDidChangeState = this._register(new Emitter());
constructor(readonly id: string) {
super();
}
prepareSession(initialState: any) {
this.lastInitialState = initialState;
return Promise.resolve(<IChat>{
async prepareSession(): Promise<IChat> {
return {
id: SimpleTestProvider.sessionId++,
username: 'test',
responderUsername: 'test',
requesterUsername: 'test',
onDidChangeState: this._onDidChangeState.event
});
}
changeState(state: any) {
this._onDidChangeState.fire(state);
};
}
async provideReply(request: IChatRequest, progress: (progress: IChatProgress) => void): Promise<{ session: IChat; followups: never[] }> {
@ -66,10 +54,14 @@ class SimpleTestProvider extends Disposable implements IChatProvider {
}
}
/** Chat provider for testing that returns used context */
class ChatProviderWithUsedContext extends SimpleTestProvider implements IChatProvider {
override provideReply(request: IChatRequest, progress: (progress: IChatProgress) => void): Promise<{ session: IChat; followups: never[] }> {
const chatAgentWithUsedContextId = 'ChatProviderWithUsedContext';
const chatAgentWithUsedContext: IChatAgent = {
id: chatAgentWithUsedContextId,
metadata: {},
async provideSlashCommands(token) {
return [];
},
async invoke(request, progress, history, token) {
progress({
documents: [
{
@ -82,9 +74,9 @@ class ChatProviderWithUsedContext extends SimpleTestProvider implements IChatPro
]
});
return super.provideReply(request, progress);
}
}
return {};
},
};
suite('Chat', () => {
const testDisposables = ensureNoDisposablesAreLeakedInTestSuite();
@ -92,6 +84,8 @@ suite('Chat', () => {
let storageService: IStorageService;
let instantiationService: TestInstantiationService;
let chatAgentService: IChatAgentService;
setup(async () => {
instantiationService = testDisposables.add(new TestInstantiationService(new ServiceCollection(
[IChatVariablesService, new MockChatVariablesService()],
@ -105,7 +99,18 @@ suite('Chat', () => {
instantiationService.stub(IChatContributionService, new TestExtensionService());
instantiationService.stub(IWorkspaceContextService, new TestContextService());
instantiationService.stub(IChatSlashCommandService, testDisposables.add(instantiationService.createInstance(ChatSlashCommandService)));
instantiationService.stub(IChatAgentService, testDisposables.add(instantiationService.createInstance(ChatAgentService)));
chatAgentService = testDisposables.add(instantiationService.createInstance(ChatAgentService));
instantiationService.stub(IChatAgentService, chatAgentService);
const agent = {
id: 'testAgent',
metadata: { isDefault: true },
async invoke(request, progress, history, token) {
return {};
},
} as IChatAgent;
testDisposables.add(chatAgentService.registerAgent(agent));
});
test('retrieveSession', async () => {
@ -123,12 +128,7 @@ suite('Chat', () => {
await session2.waitForInitialization();
session2!.addRequest({ parts: [], text: 'request 2' });
assert.strictEqual(provider1.lastInitialState, undefined);
assert.strictEqual(provider2.lastInitialState, undefined);
provider1.changeState({ state: 'provider1_state' });
provider2.changeState({ state: 'provider2_state' });
storageService.flush();
const testService2 = testDisposables.add(instantiationService.createInstance(ChatService));
testDisposables.add(testService2.registerProvider(provider1));
testDisposables.add(testService2.registerProvider(provider2));
@ -136,8 +136,8 @@ suite('Chat', () => {
await retrieved1!.waitForInitialization();
const retrieved2 = testDisposables.add(testService2.getOrRestoreSession(session2.sessionId)!);
await retrieved2!.waitForInitialization();
assert.deepStrictEqual(provider1.lastInitialState, { state: 'provider1_state' });
assert.deepStrictEqual(provider2.lastInitialState, { state: 'provider2_state' });
assert.deepStrictEqual(retrieved1.getRequests()[0]?.message.text, 'request 1');
assert.deepStrictEqual(retrieved2.getRequests()[0]?.message.text, 'request 2');
});
test('Handles failed session startup', async () => {
@ -172,10 +172,7 @@ suite('Chat', () => {
testDisposables.add(testService.registerProvider({
id,
displayName: 'Test',
prepareSession: function (initialState: IPersistedChatState | undefined, token: CancellationToken): ProviderResult<IChat | undefined> {
throw new Error('Function not implemented.');
},
provideReply: function (request: IChatRequest, progress: (progress: IChatProgress) => void, token: CancellationToken): ProviderResult<IChatResponse> {
prepareSession: function (token: CancellationToken): ProviderResult<IChat | undefined> {
throw new Error('Function not implemented.');
}
}));
@ -184,45 +181,13 @@ suite('Chat', () => {
testDisposables.add(testService.registerProvider({
id,
displayName: 'Test',
prepareSession: function (initialState: IPersistedChatState | undefined, token: CancellationToken): ProviderResult<IChat | undefined> {
throw new Error('Function not implemented.');
},
provideReply: function (request: IChatRequest, progress: (progress: IChatProgress) => void, token: CancellationToken): ProviderResult<IChatResponse> {
prepareSession: function (token: CancellationToken): ProviderResult<IChat | undefined> {
throw new Error('Function not implemented.');
}
}));
}, 'Expected to throw for dupe provider');
});
test('getSlashCommands', async () => {
const testService = testDisposables.add(instantiationService.createInstance(ChatService));
const provider = testDisposables.add(new class extends SimpleTestProvider {
constructor() {
super('testProvider');
}
provideSlashCommands(): ProviderResult<ISlashCommand[]> {
return [
{
command: 'command',
detail: 'detail',
sortText: 'sortText',
}
];
}
});
testDisposables.add(testService.registerProvider(provider));
const model = testDisposables.add(testService.startSession('testProvider', CancellationToken.None));
const commands = await testService.getSlashCommands(model.sessionId, CancellationToken.None);
assert.strictEqual(commands?.length, 1);
assert.strictEqual(commands?.[0].command, 'command');
assert.strictEqual(commands?.[0].detail, 'detail');
assert.strictEqual(commands?.[0].sortText, 'sortText');
});
test('sendRequestToProvider', async () => {
const testService = testDisposables.add(instantiationService.createInstance(ChatService));
testDisposables.add(testService.registerProvider(testDisposables.add(new SimpleTestProvider('testProvider'))));
@ -249,18 +214,16 @@ suite('Chat', () => {
});
test('can serialize', async () => {
testDisposables.add(chatAgentService.registerAgent(chatAgentWithUsedContext));
const testService = testDisposables.add(instantiationService.createInstance(ChatService));
const providerId = 'ChatProviderWithUsedContext';
testDisposables.add(testService.registerProvider(testDisposables.add(new SimpleTestProvider('testProvider'))));
testDisposables.add(testService.registerProvider(testDisposables.add(new ChatProviderWithUsedContext(providerId))));
const model = testDisposables.add(testService.startSession(providerId, CancellationToken.None));
const model = testDisposables.add(testService.startSession('testProvider', CancellationToken.None));
assert.strictEqual(model.getRequests().length, 0);
await assertSnapshot(model.toExport());
const response = await testService.sendRequest(model.sessionId, 'test request');
const response = await testService.sendRequest(model.sessionId, `@${chatAgentWithUsedContextId} test request`);
assert(response);
await response.responseCompletePromise;
@ -271,21 +234,18 @@ suite('Chat', () => {
});
test('can deserialize', async () => {
let serializedChatData: ISerializableChatData;
const providerId = 'ChatProviderWithUsedContext';
testDisposables.add(chatAgentService.registerAgent(chatAgentWithUsedContext));
// create the first service, send request, get response, and serialize the state
{ // serapate block to not leak variables in outer scope
const testService = testDisposables.add(instantiationService.createInstance(ChatService));
testDisposables.add(testService.registerProvider(testDisposables.add(new SimpleTestProvider('testProvider'))));
testDisposables.add(testService.registerProvider(testDisposables.add(new ChatProviderWithUsedContext(providerId))));
const chatModel1 = testDisposables.add(testService.startSession(providerId, CancellationToken.None));
const chatModel1 = testDisposables.add(testService.startSession('testProvider', CancellationToken.None));
assert.strictEqual(chatModel1.getRequests().length, 0);
const response = await testService.sendRequest(chatModel1.sessionId, 'test request');
const response = await testService.sendRequest(chatModel1.sessionId, `@${chatAgentWithUsedContextId} test request`);
assert(response);
await response.responseCompletePromise;
@ -296,17 +256,11 @@ suite('Chat', () => {
// try deserializing the state into a new service
const testService2 = testDisposables.add(instantiationService.createInstance(ChatService));
testDisposables.add(testService2.registerProvider(testDisposables.add(new ChatProviderWithUsedContext(providerId))));
testDisposables.add(testService2.registerProvider(testDisposables.add(new SimpleTestProvider('testProvider'))));
const chatModel2 = testService2.loadSessionFromContent(serializedChatData);
assert(chatModel2);
// should `loadSessionFromContent` return `ChatModel` that's disposable instead of `IChatModel`?
testDisposables.add({
dispose: () => testService2.clearSession(serializedChatData.sessionId)
});
await assertSnapshot(chatModel2.toExport());
});
});

View file

@ -11,7 +11,6 @@ export const allApiProposals = Object.freeze({
authSession: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.authSession.d.ts',
canonicalUriProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.canonicalUriProvider.d.ts',
chat: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chat.d.ts',
chatAgents: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatAgents.d.ts',
chatAgents2: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatAgents2.d.ts',
chatAgents2Additions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts',
chatProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatProvider.d.ts',

View file

@ -1,39 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
declare module 'vscode' {
export interface ChatAgentContext {
history: ChatMessage[];
}
export interface ChatAgentResponse {
message: MarkdownString | InteractiveProgressFileTree;
}
export interface ChatAgentResult {
followUp?: InteractiveSessionFollowup[];
}
export interface ChatAgentCommand {
name: string;
description: string;
}
export interface ChatAgentMetadata {
description: string;
fullName?: string;
icon?: Uri;
subCommands: ChatAgentCommand[];
}
export interface ChatAgent {
(prompt: ChatMessage, context: ChatAgentContext, progress: Progress<ChatAgentResponse>, token: CancellationToken): Thenable<ChatAgentResult | void>;
}
export namespace chat {
export function registerAgent(id: string, agent: ChatAgent, metadata: ChatAgentMetadata): Disposable;
}
}

View file

@ -85,9 +85,6 @@ declare module 'vscode' {
handleInteractiveEditorResponseFeedback?(session: S, response: R, kind: InteractiveEditorResponseFeedbackKind): void;
}
export interface InteractiveSessionState { }
export interface InteractiveSessionParticipantInformation {
name: string;
@ -101,81 +98,8 @@ declare module 'vscode' {
requester: InteractiveSessionParticipantInformation;
responder: InteractiveSessionParticipantInformation;
inputPlaceholder?: string;
saveState?(): InteractiveSessionState;
}
export interface InteractiveSessionRequestArgs {
command: string;
args: any;
}
export interface InteractiveRequest {
session: InteractiveSession;
message: string;
}
export interface InteractiveResponseErrorDetails {
message: string;
responseIsIncomplete?: boolean;
responseIsFiltered?: boolean;
}
export interface InteractiveResponseForProgress {
errorDetails?: InteractiveResponseErrorDetails;
}
export interface InteractiveContentReference {
reference: Uri | Location;
}
export interface InteractiveInlineContentReference {
inlineReference: Uri | Location;
title?: string; // eg symbol name
}
export interface InteractiveProgressContent {
content: string | MarkdownString;
}
export interface InteractiveProgressId {
responseId: string;
}
export interface InteractiveProgressTask {
placeholder: string;
resolvedContent: Thenable<InteractiveProgressContent | InteractiveProgressFileTree>;
}
export interface FileTreeData {
label: string;
uri: Uri;
children?: FileTreeData[];
}
export interface InteractiveProgressFileTree {
treeData: FileTreeData;
}
export interface DocumentContext {
uri: Uri;
version: number;
ranges: Range[];
}
export interface InteractiveProgressUsedContext {
documents: DocumentContext[];
}
export type InteractiveProgress =
| InteractiveProgressContent
| InteractiveProgressId
| InteractiveProgressTask
| InteractiveProgressFileTree
| InteractiveProgressUsedContext
| InteractiveContentReference
| InteractiveInlineContentReference;
export interface InteractiveResponseCommand {
commandId: string;
args?: any[];
@ -183,40 +107,18 @@ declare module 'vscode' {
when?: string;
}
export interface InteractiveSessionSlashCommand {
command: string;
kind: CompletionItemKind;
detail?: string;
shouldRepopulate?: boolean;
followupPlaceholder?: string;
executeImmediately?: boolean;
yieldTo?: ReadonlyArray<{ readonly command: string }>;
}
export interface InteractiveSessionReplyFollowup {
message: string;
tooltip?: string;
title?: string;
// Extensions can put any serializable data here, such as an ID/version
metadata?: any;
}
export type InteractiveSessionFollowup = InteractiveSessionReplyFollowup | InteractiveResponseCommand;
export type InteractiveWelcomeMessageContent = string | MarkdownString | InteractiveSessionReplyFollowup[];
export interface InteractiveSessionProvider<S extends InteractiveSession = InteractiveSession> {
provideWelcomeMessage?(token: CancellationToken): ProviderResult<InteractiveWelcomeMessageContent[]>;
provideSampleQuestions?(token: CancellationToken): ProviderResult<InteractiveSessionReplyFollowup[]>;
provideFollowups?(session: S, token: CancellationToken): ProviderResult<(string | InteractiveSessionFollowup)[]>;
provideSlashCommands?(session: S, token: CancellationToken): ProviderResult<InteractiveSessionSlashCommand[]>;
prepareSession(initialState: InteractiveSessionState | undefined, token: CancellationToken): ProviderResult<S>;
provideResponseWithProgress(request: InteractiveRequest, progress: Progress<InteractiveProgress>, token: CancellationToken): ProviderResult<InteractiveResponseForProgress>;
// eslint-disable-next-line local/vscode-dts-provider-naming
removeRequest(session: S, requestId: string): void;
prepareSession(token: CancellationToken): ProviderResult<S>;
}
export interface InteractiveSessionDynamicRequest {
@ -224,12 +126,6 @@ declare module 'vscode' {
* The message that will be displayed in the UI
*/
message: string;
/**
* Any extra metadata/context that will go to the provider.
* NOTE not actually used yet.
*/
metadata?: any;
}
export namespace interactive {

View file

@ -13,8 +13,6 @@ declare module 'vscode' {
export interface InteractiveSessionVoteAction {
// eslint-disable-next-line local/vscode-dts-string-type-literals
kind: 'vote';
// sessionId: string;
responseId: string;
direction: InteractiveSessionVoteDirection;
}
@ -27,8 +25,6 @@ declare module 'vscode' {
export interface InteractiveSessionCopyAction {
// eslint-disable-next-line local/vscode-dts-string-type-literals
kind: 'copy';
// sessionId: string;
responseId: string;
codeBlockIndex: number;
copyType: InteractiveSessionCopyKind;
copiedCharacters: number;
@ -39,8 +35,6 @@ declare module 'vscode' {
export interface InteractiveSessionInsertAction {
// eslint-disable-next-line local/vscode-dts-string-type-literals
kind: 'insert';
// sessionId: string;
responseId: string;
codeBlockIndex: number;
totalCharacters: number;
newFile?: boolean;
@ -49,8 +43,6 @@ declare module 'vscode' {
export interface InteractiveSessionTerminalAction {
// eslint-disable-next-line local/vscode-dts-string-type-literals
kind: 'runInTerminal';
// sessionId: string;
responseId: string;
codeBlockIndex: number;
languageId?: string;
}