@codingame/monaco-vscode-extensions-service-override
Version:
VSCode public API plugged on the monaco editor - extensions service-override
287 lines (283 loc) • 13.8 kB
JavaScript
import { __decorate, __param } from '@codingame/monaco-vscode-api/external/tslib/tslib.es6';
import { DeferredPromise, AsyncIterableSource } from '@codingame/monaco-vscode-api/vscode/vs/base/common/async';
import { VSBuffer } from '@codingame/monaco-vscode-api/vscode/vs/base/common/buffer';
import { toErrorMessage } from '@codingame/monaco-vscode-api/vscode/vs/base/common/errorMessage';
import { transformErrorFromSerialization, transformErrorForSerialization } from '@codingame/monaco-vscode-api/vscode/vs/base/common/errors';
import { Emitter, Event } from '@codingame/monaco-vscode-api/vscode/vs/base/common/event';
import { DisposableStore, DisposableMap, Disposable, toDisposable } from '@codingame/monaco-vscode-api/vscode/vs/base/common/lifecycle';
import { URI } from '@codingame/monaco-vscode-api/vscode/vs/base/common/uri';
import { localize } from '@codingame/monaco-vscode-api/vscode/vs/nls';
import { ILogService } from '@codingame/monaco-vscode-api/vscode/vs/platform/log/common/log.service';
import { resizeImage } from '@codingame/monaco-vscode-api/vscode/vs/workbench/contrib/chat/browser/chatImageUtils';
import { ILanguageModelIgnoredFilesService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/contrib/chat/common/ignoredFiles.service';
import { ILanguageModelsService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/contrib/chat/common/languageModels.service';
import { IAuthenticationAccessService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/authentication/browser/authenticationAccessService.service';
import { INTERNAL_AUTH_PROVIDER_PREFIX } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/authentication/common/authentication';
import { IAuthenticationService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/authentication/common/authentication.service';
import { extHostNamedCustomer } from '../../services/extensions/common/extHostCustomers.js';
import { IExtensionService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/extensions/common/extensions.service';
import { SerializableObjectWithBuffers } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/extensions/common/proxyIdentifier';
import { ExtHostContext, MainContext } from '@codingame/monaco-vscode-api/vscode/vs/workbench/api/common/extHost.protocol';
import { LanguageModelError } from '@codingame/monaco-vscode-api/vscode/vs/workbench/api/common/extHostTypes';
let MainThreadLanguageModels = class MainThreadLanguageModels {
constructor(
extHostContext,
_chatProviderService,
_logService,
_authenticationService,
_authenticationAccessService,
_extensionService,
_ignoredFilesService
) {
this._chatProviderService = _chatProviderService;
this._logService = _logService;
this._authenticationService = _authenticationService;
this._authenticationAccessService = _authenticationAccessService;
this._extensionService = _extensionService;
this._ignoredFilesService = _ignoredFilesService;
this._store = ( new DisposableStore());
this._providerRegistrations = ( new DisposableMap());
this._lmProviderChange = ( new Emitter());
this._pendingProgress = ( new Map());
this._ignoredFileProviderRegistrations = ( new DisposableMap());
this._proxy = ( extHostContext.getProxy(ExtHostContext.ExtHostChatProvider));
}
dispose() {
this._lmProviderChange.dispose();
this._providerRegistrations.dispose();
this._ignoredFileProviderRegistrations.dispose();
this._store.dispose();
}
$registerLanguageModelProvider(vendor) {
const disposables = ( new DisposableStore());
try {
disposables.add(this._chatProviderService.registerLanguageModelProvider(vendor, {
onDidChange: Event.filter(this._lmProviderChange.event, e => e.vendor === vendor, disposables),
provideLanguageModelChatInfo: async (options, token) => {
const modelsAndIdentifiers = await this._proxy.$provideLanguageModelChatInfo(vendor, options, token);
modelsAndIdentifiers.forEach(m => {
if (m.metadata.auth) {
disposables.add(
this._registerAuthenticationProvider(m.metadata.extension, m.metadata.auth)
);
}
});
return modelsAndIdentifiers;
},
sendChatRequest: async (modelId, messages, from, options, token) => {
const requestId = (Math.random() * 1e6) | 0;
const defer = ( new DeferredPromise());
const stream = new AsyncIterableSource();
try {
this._pendingProgress.set(requestId, {
defer,
stream
});
await Promise.all(( messages.flatMap(msg => msg.content).filter(part => part.type === "image_url").map(async part => {
part.value.data = VSBuffer.wrap(await resizeImage(part.value.data.buffer));
})));
await this._proxy.$startChatRequest(modelId, requestId, from, ( new SerializableObjectWithBuffers(messages)), options, token);
} catch (err) {
this._pendingProgress.delete(requestId);
throw err;
}
return {
result: defer.p,
stream: stream.asyncIterable
};
},
provideTokenCount: (modelId, str, token) => {
return this._proxy.$provideTokenLength(modelId, str, token);
}
}));
this._providerRegistrations.set(vendor, disposables);
} catch (err) {
disposables.dispose();
throw err;
}
}
$onLMProviderChange(vendor) {
this._lmProviderChange.fire({
vendor
});
}
async $reportResponsePart(requestId, chunk) {
const data = this._pendingProgress.get(requestId);
this._logService.trace("[LM] report response PART", Boolean(data), requestId, chunk);
if (data) {
data.stream.emitOne(chunk.value);
}
}
async $reportResponseDone(requestId, err) {
const data = this._pendingProgress.get(requestId);
this._logService.trace("[LM] report response DONE", Boolean(data), requestId, err);
if (data) {
this._pendingProgress.delete(requestId);
if (err) {
const error = LanguageModelError.tryDeserialize(err) ?? transformErrorFromSerialization(err);
data.stream.reject(error);
data.defer.error(error);
} else {
data.stream.resolve();
data.defer.complete(undefined);
}
}
}
$unregisterProvider(vendor) {
this._providerRegistrations.deleteAndDispose(vendor);
}
$selectChatModels(selector) {
return this._chatProviderService.selectLanguageModels(selector);
}
async $tryStartChatRequest(extension, modelIdentifier, requestId, messages, options, token) {
this._logService.trace("[CHAT] request STARTED", extension.value, requestId);
let response;
try {
response = await this._chatProviderService.sendChatRequest(modelIdentifier, extension, messages.value, options, token);
} catch (err) {
this._logService.error("[CHAT] request FAILED", extension.value, requestId, err);
throw err;
}
const streaming = (async () => {
try {
for await (const part of response.stream) {
this._logService.trace("[CHAT] request PART", extension.value, requestId, part);
await this._proxy.$acceptResponsePart(requestId, ( new SerializableObjectWithBuffers(part)));
}
this._logService.trace("[CHAT] request DONE", extension.value, requestId);
} catch (err) {
this._logService.error(
"[CHAT] extension request ERRORED in STREAM",
toErrorMessage(err, true),
extension.value,
requestId
);
this._proxy.$acceptResponseDone(requestId, transformErrorForSerialization(err));
}
})();
Promise.allSettled([response.result, streaming]).then(() => {
this._logService.debug("[CHAT] extension request DONE", extension.value, requestId);
this._proxy.$acceptResponseDone(requestId, undefined);
}, err => {
this._logService.error(
"[CHAT] extension request ERRORED",
toErrorMessage(err, true),
extension.value,
requestId
);
this._proxy.$acceptResponseDone(requestId, transformErrorForSerialization(err));
});
}
$countTokens(modelId, value, token) {
return this._chatProviderService.computeTokenLength(modelId, value, token);
}
_registerAuthenticationProvider(extension, auth) {
const authProviderId = INTERNAL_AUTH_PROVIDER_PREFIX + extension.value;
if (this._authenticationService.getProviderIds().includes(authProviderId)) {
return Disposable.None;
}
const accountLabel = auth.accountLabel ?? ( localize(2623, "Language Models"));
const disposables = ( new DisposableStore());
const provider = ( new LanguageModelAccessAuthProvider(authProviderId, auth.providerLabel, accountLabel));
this._authenticationService.registerAuthenticationProvider(authProviderId, provider);
disposables.add(toDisposable(() => {
this._authenticationService.unregisterAuthenticationProvider(authProviderId);
provider.dispose();
}));
disposables.add(
this._authenticationAccessService.onDidChangeExtensionSessionAccess(async e => {
const allowedExtensions = this._authenticationAccessService.readAllowedExtensions(authProviderId, accountLabel);
const accessList = [];
for (const allowedExtension of allowedExtensions) {
const from = await this._extensionService.getExtension(allowedExtension.id);
if (from) {
accessList.push({
from: from.identifier,
to: extension,
enabled: allowedExtension.allowed ?? true
});
}
}
this._proxy.$updateModelAccesslist(accessList);
})
);
return disposables;
}
$fileIsIgnored(uri, token) {
return this._ignoredFilesService.fileIsIgnored(URI.revive(uri), token);
}
$registerFileIgnoreProvider(handle) {
this._ignoredFileProviderRegistrations.set(handle, this._ignoredFilesService.registerIgnoredFileProvider({
isFileIgnored: async (uri, token) => this._proxy.$isFileIgnored(handle, uri, token)
}));
}
$unregisterFileIgnoreProvider(handle) {
this._ignoredFileProviderRegistrations.deleteAndDispose(handle);
}
};
MainThreadLanguageModels = __decorate(
[extHostNamedCustomer(MainContext.MainThreadLanguageModels), ( __param(1, ILanguageModelsService)), ( __param(2, ILogService)), ( __param(3, IAuthenticationService)), ( __param(4, IAuthenticationAccessService)), ( __param(5, IExtensionService)), ( __param(6, ILanguageModelIgnoredFilesService))],
MainThreadLanguageModels
);
class LanguageModelAccessAuthProvider {
constructor(id, label, _accountLabel) {
this.id = id;
this.label = label;
this._accountLabel = _accountLabel;
this.supportsMultipleAccounts = false;
this._onDidChangeSessions = ( new Emitter());
this.onDidChangeSessions = this._onDidChangeSessions.event;
}
async getSessions(scopes) {
if (scopes === undefined && !this._session) {
return [];
}
if (this._session) {
return [this._session];
}
return [await this.createSession(scopes || [])];
}
async createSession(scopes) {
this._session = this._createFakeSession(scopes);
this._onDidChangeSessions.fire({
added: [this._session],
changed: [],
removed: []
});
return this._session;
}
removeSession(sessionId) {
if (this._session) {
this._onDidChangeSessions.fire({
added: [],
changed: [],
removed: [this._session]
});
this._session = undefined;
}
return Promise.resolve();
}
confirmation(extensionName, _recreatingSession) {
return localize(
2624,
"The extension '{0}' wants to access the language models provided by {1}.",
extensionName,
this.label
);
}
_createFakeSession(scopes) {
return {
id: "fake-session",
account: {
id: this.id,
label: this._accountLabel
},
accessToken: "fake-access-token",
scopes
};
}
dispose() {
this._onDidChangeSessions.dispose();
}
}
export { MainThreadLanguageModels };