-
Notifications
You must be signed in to change notification settings - Fork 29.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #188297 from microsoft/joh/thoughtless-haddock
joh/thoughtless-haddock
- Loading branch information
Showing
19 changed files
with
875 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
/*--------------------------------------------------------------------------------------------- | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. See License.txt in the project root for license information. | ||
*--------------------------------------------------------------------------------------------*/ | ||
|
||
import { CancellationToken } from 'vs/base/common/cancellation'; | ||
import { DisposableMap } from 'vs/base/common/lifecycle'; | ||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; | ||
import { ILogService } from 'vs/platform/log/common/log'; | ||
import { IProgress, Progress } from 'vs/platform/progress/common/progress'; | ||
import { ExtHostChatProviderShape, ExtHostContext, MainContext, MainThreadChatProviderShape } from 'vs/workbench/api/common/extHost.protocol'; | ||
import { IChatResponseProviderMetadata, IChatResponseFragment, IChatProviderService, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; | ||
import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; | ||
|
||
@extHostNamedCustomer(MainContext.MainThreadChatProvider) | ||
export class MainThreadChatProvider implements MainThreadChatProviderShape { | ||
|
||
private readonly _proxy: ExtHostChatProviderShape; | ||
private readonly _providerRegistrations = new DisposableMap<number>(); | ||
private readonly _pendingProgress = new Map<number, IProgress<IChatResponseFragment>>(); | ||
|
||
constructor( | ||
extHostContext: IExtHostContext, | ||
@IChatProviderService private readonly _chatProviderService: IChatProviderService, | ||
@ILogService private readonly _logService: ILogService, | ||
) { | ||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatProvider); | ||
} | ||
|
||
dispose(): void { | ||
this._providerRegistrations.dispose(); | ||
} | ||
|
||
$registerProvider(handle: number, identifier: string, metadata: IChatResponseProviderMetadata): void { | ||
const registration = this._chatProviderService.registerChatResponseProvider(identifier, { | ||
metadata, | ||
provideChatResponse: async (messages, options, progress, token) => { | ||
const requestId = (Math.random() * 1e6) | 0; | ||
this._pendingProgress.set(requestId, progress); | ||
try { | ||
await this._proxy.$provideChatResponse(handle, requestId, messages, options, token); | ||
} finally { | ||
this._pendingProgress.delete(requestId); | ||
} | ||
} | ||
}); | ||
this._providerRegistrations.set(handle, registration); | ||
} | ||
|
||
async $handleProgressChunk(requestId: number, chunk: IChatResponseFragment): Promise<void> { | ||
this._pendingProgress.get(requestId)?.report(chunk); | ||
} | ||
|
||
$unregisterProvider(handle: number): void { | ||
this._providerRegistrations.deleteAndDispose(handle); | ||
} | ||
|
||
async $fetchResponse(extension: ExtensionIdentifier, providerId: string, requestId: number, messages: IChatMessage[], options: {}, token: CancellationToken): Promise<any> { | ||
this._logService.debug('[CHAT] extension request STARTED', extension.value, requestId); | ||
|
||
const task = this._chatProviderService.fetchChatResponse(providerId, messages, options, new Progress(value => { | ||
this._proxy.$handleResponseFragment(requestId, value); | ||
}), token); | ||
|
||
task.catch(err => { | ||
this._logService.error('[CHAT] extension request ERRORED', err, extension.value, requestId); | ||
}).finally(() => { | ||
this._logService.debug('[CHAT] extension request DONE', extension.value, requestId); | ||
}); | ||
|
||
return task; | ||
} | ||
} |
61 changes: 61 additions & 0 deletions
61
src/vs/workbench/api/browser/mainThreadChatSlashCommands.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
/*--------------------------------------------------------------------------------------------- | ||
* 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 { IProgress } from 'vs/platform/progress/common/progress'; | ||
import { ExtHostChatSlashCommandsShape, ExtHostContext, MainContext, MainThreadChatSlashCommandsShape } from 'vs/workbench/api/common/extHost.protocol'; | ||
import { IChatSlashCommandService, IChatSlashFragment } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; | ||
import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; | ||
|
||
|
||
@extHostNamedCustomer(MainContext.MainThreadChatSlashCommands) | ||
export class MainThreadChatSlashCommands implements MainThreadChatSlashCommandsShape { | ||
|
||
private readonly _commands = new DisposableMap<number>; | ||
private readonly _pendingProgress = new Map<number, IProgress<IChatSlashFragment>>(); | ||
private readonly _proxy: ExtHostChatSlashCommandsShape; | ||
|
||
constructor( | ||
extHostContext: IExtHostContext, | ||
@IChatSlashCommandService private readonly _chatSlashCommandService: IChatSlashCommandService | ||
) { | ||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatSlashCommands); | ||
} | ||
|
||
dispose(): void { | ||
this._commands.clearAndDisposeAll(); | ||
} | ||
|
||
$registerCommand(handle: number, name: string, detail: string): void { | ||
|
||
if (!this._chatSlashCommandService.hasCommand(name)) { | ||
// dynamic slash commands! | ||
this._chatSlashCommandService.registerSlashData({ | ||
name, | ||
id: name, | ||
detail | ||
}); | ||
} | ||
|
||
const d = this._chatSlashCommandService.registerSlashCallback(name, async (prompt, progress, history, token) => { | ||
const requestId = Math.random(); | ||
this._pendingProgress.set(requestId, progress); | ||
try { | ||
await this._proxy.$executeCommand(handle, requestId, prompt, { history }, token); | ||
} finally { | ||
this._pendingProgress.delete(requestId); | ||
} | ||
}); | ||
this._commands.set(handle, d); | ||
} | ||
|
||
async $handleProgressChunk(requestId: number, chunk: IChatSlashFragment): Promise<void> { | ||
this._pendingProgress.get(requestId)?.report(chunk); | ||
} | ||
|
||
$unregisterCommand(handle: number): void { | ||
this._commands.deleteAndDispose(handle); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
/*--------------------------------------------------------------------------------------------- | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. See License.txt in the project root for license information. | ||
*--------------------------------------------------------------------------------------------*/ | ||
|
||
import { CancellationToken } from 'vs/base/common/cancellation'; | ||
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; | ||
import { ILogService } from 'vs/platform/log/common/log'; | ||
import { ExtHostChatProviderShape, IMainContext, MainContext, MainThreadChatProviderShape } from 'vs/workbench/api/common/extHost.protocol'; | ||
import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; | ||
import type * as vscode from 'vscode'; | ||
import { Progress } from 'vs/platform/progress/common/progress'; | ||
import { IChatMessage, IChatResponseFragment } from 'vs/workbench/contrib/chat/common/chatProvider'; | ||
import { ExtensionIdentifier, ExtensionIdentifierMap } from 'vs/platform/extensions/common/extensions'; | ||
|
||
type ProviderData = { | ||
readonly extension: ExtensionIdentifier; | ||
readonly provider: vscode.ChatResponseProvider; | ||
}; | ||
|
||
export class ExtHostChatProvider implements ExtHostChatProviderShape { | ||
|
||
private static _idPool = 1; | ||
|
||
private readonly _proxy: MainThreadChatProviderShape; | ||
private readonly _providers = new Map<number, ProviderData>(); | ||
|
||
constructor( | ||
mainContext: IMainContext, | ||
private readonly _logService: ILogService, | ||
) { | ||
this._proxy = mainContext.getProxy(MainContext.MainThreadChatProvider); | ||
} | ||
|
||
registerProvider(extension: ExtensionIdentifier, identifier: string, provider: vscode.ChatResponseProvider, metadata: vscode.ChatResponseProviderMetadata): IDisposable { | ||
|
||
const handle = ExtHostChatProvider._idPool++; | ||
this._providers.set(handle, { extension, provider }); | ||
this._proxy.$registerProvider(handle, identifier, { extension, displayName: metadata.name ?? extension.value }); | ||
|
||
return toDisposable(() => { | ||
this._proxy.$unregisterProvider(handle); | ||
this._providers.delete(handle); | ||
}); | ||
} | ||
|
||
async $provideChatResponse(handle: number, requestId: number, messages: IChatMessage[], options: { [name: string]: any }, token: CancellationToken): Promise<any> { | ||
const data = this._providers.get(handle); | ||
if (!data) { | ||
return; | ||
} | ||
const progress = new Progress<vscode.ChatResponseFragment>(async fragment => { | ||
if (token.isCancellationRequested) { | ||
this._logService.warn(`[CHAT](${data.extension.value}) CANNOT send progress because the REQUEST IS CANCELLED`); | ||
return; | ||
} | ||
await this._proxy.$handleProgressChunk(requestId, { index: fragment.index, part: fragment.part }); | ||
}, { async: true }); | ||
|
||
return data.provider.provideChatResponse(messages.map(typeConvert.ChatMessage.to), options, progress, token); | ||
} | ||
|
||
//#region --- making request | ||
|
||
private readonly _pendingRequest = new Map<number, vscode.Progress<vscode.ChatResponseFragment>>(); | ||
|
||
private readonly _chatAccessAllowList = new ExtensionIdentifierMap<Promise<unknown>>(); | ||
|
||
allowListExtensionWhile(extension: ExtensionIdentifier, promise: Promise<unknown>): void { | ||
this._chatAccessAllowList.set(extension, promise); | ||
promise.finally(() => this._chatAccessAllowList.delete(extension)); | ||
} | ||
|
||
async requestChatResponseProvider(from: ExtensionIdentifier, identifier: string): Promise<vscode.ChatAccess> { | ||
// check if a UI command is running/active | ||
|
||
if (!this._chatAccessAllowList.has(from)) { | ||
throw new Error('Extension is NOT allowed to make chat requests'); | ||
} | ||
|
||
const that = this; | ||
|
||
return { | ||
get isRevoked() { | ||
return !that._chatAccessAllowList.has(from); | ||
}, | ||
async makeRequest(messages, options, progress, token) { | ||
|
||
if (!that._chatAccessAllowList.has(from)) { | ||
throw new Error('Access to chat has been revoked'); | ||
} | ||
|
||
const requestId = (Math.random() * 1e6) | 0; | ||
that._pendingRequest.set(requestId, progress); | ||
try { | ||
await that._proxy.$fetchResponse(from, identifier, requestId, messages.map(typeConvert.ChatMessage.from), options, token); | ||
} finally { | ||
that._pendingRequest.delete(requestId); | ||
} | ||
}, | ||
}; | ||
} | ||
|
||
async $handleResponseFragment(requestId: number, chunk: IChatResponseFragment): Promise<void> { | ||
this._pendingRequest.get(requestId)?.report(chunk); | ||
} | ||
} |
Oops, something went wrong.