Path: blob/main/extensions/copilot/src/platform/proxyModels/node/proxyModelsService.ts
13400 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { isDeepStrictEqual } from 'util';6import { ErrorUtils } from '../../../util/common/errors';7import { CancellationToken, CancellationTokenSource } from '../../../util/vs/base/common/cancellation';8import { Emitter } from '../../../util/vs/base/common/event';9import { Disposable } from '../../../util/vs/base/common/lifecycle';10import { autorun, observableFromEvent } from '../../../util/vs/base/common/observable';11import { CopilotToken } from '../../authentication/common/copilotToken';12import { ICopilotTokenStore } from '../../authentication/common/copilotTokenStore';13import { ICAPIClientService } from '../../endpoint/common/capiClient';14import { WireTypes } from '../../inlineEdits/common/dataTypes/inlineEditsModelsTypes';15import { ILogService } from '../../log/common/logService';16import { IFetcherService, Response } from '../../networking/common/fetcherService';17import { IProxyModelsService } from '../common/proxyModelsService';1819export class ProxyModelsService extends Disposable implements IProxyModelsService {20readonly _serviceBrand: undefined;2122private readonly _onModelListUpdated = this._register(new Emitter<void>());23public readonly onModelListUpdated = this._onModelListUpdated.event;2425private _models: WireTypes.ModelList.t | undefined;2627constructor(28@ICopilotTokenStore private readonly _tokenStore: ICopilotTokenStore,29@ICAPIClientService private readonly _capiClient: ICAPIClientService,30@IFetcherService private readonly _fetchService: IFetcherService,31@ILogService private readonly _logService: ILogService,32) {33super();3435const copilotTokenObs = observableFromEvent(this, this._tokenStore.onDidStoreUpdate, () => this._tokenStore.copilotToken);3637this._register(autorun(reader => {38const copilotToken = copilotTokenObs.read(reader);39const cts = new CancellationTokenSource();40this._fetchLatestModels(copilotToken, cts.token).then(models => {41if (models === undefined) {42return;43}44if (cts.token.isCancellationRequested) {45return;46}47if (isDeepStrictEqual(this._models, models)) {48return;49}50this._models = models;51this._onModelListUpdated.fire();52}).catch((e: unknown) => {53const err = ErrorUtils.fromUnknown(e);54this._logService.error(err, 'Failed to fetch models in autorun');55});56reader.store.add({ dispose: () => cts.dispose(true) });57}));58}5960get models(): WireTypes.ModelList.t | undefined {61return this._models;62}6364get nesModels(): WireTypes.Model.t[] | undefined {65return this._models?.models.filter(model => model.serviceType === 'NESChat');66}6768get instantApplyModels(): WireTypes.Model.t[] | undefined {69return this._models?.models.filter(model => model.serviceType === 'InstantApplyChat');70}7172private async _fetchLatestModels(copilotToken: CopilotToken | undefined, token: CancellationToken): Promise<WireTypes.ModelList.t | undefined> {73if (!copilotToken) {74return undefined;75}7677const url = `${this._capiClient.proxyBaseURL}/models`;7879const abortController = this._fetchService.makeAbortController();80const disposable = token.onCancellationRequested(() => abortController.abort());8182let r: Response;83try {84r = await this._fetchService.fetch(url, {85headers: {86'Authorization': `Bearer ${copilotToken.token}`,87},88method: 'GET',89timeout: 10_000,90callSite: 'proxy-models',91signal: abortController.signal,92});93} catch (e: unknown) {94const err = ErrorUtils.fromUnknown(e);95this._logService.error(err, 'Failed to fetch model list');96return;97} finally {98disposable.dispose();99}100101if (!r.ok) {102this._logService.error(`Failed to fetch model list: ${r.status} ${r.statusText}`);103return;104}105106try {107const jsonData: unknown = await r.json();108const validatedData = WireTypes.ModelList.validator.validate(jsonData);109if (validatedData.error) {110throw new Error(`Invalid /models response data: ${validatedData.error.message}`); // TODO@ulugbekna: add telemetry111}112return validatedData.content;113} catch (e: unknown) {114const err = ErrorUtils.fromUnknown(e);115this._logService.error(err, 'Failed to process /models response');116return;117}118}119120}121122123