Path: blob/main/extensions/copilot/src/extension/byok/vscode-node/byokStorageService.ts
13399 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*--------------------------------------------------------------------------------------------*/4import { IVSCodeExtensionContext } from '../../../platform/extContext/common/extensionContext';5import { BYOKAuthType, BYOKModelCapabilities } from '../../byok/common/byokProvider';67export interface StoredModelConfig {8deploymentUrl?: string;9isRegistered?: boolean; // Will be undefined for now but eventually storage will update to be true / false.10isCustomModel?: boolean; // Will be undefined for now but eventually storage will update to be true / false.11modelCapabilities?: BYOKModelCapabilities;12}1314export interface IBYOKStorageService {15/**16* Get API key for a provider or model17*/18getAPIKey(providerName: string, modelId?: string): Promise<string | undefined>;1920/**21* Store API key for a provider or model based on auth type22*/23storeAPIKey(providerName: string, apiKey: string, authType: BYOKAuthType, modelId?: string): Promise<void>;2425/**26* Delete API key for a provider or model based on auth type27*/28deleteAPIKey(providerName: string, authType: BYOKAuthType, modelId?: string): Promise<void>;2930/**31* Get all stored model configurations for a provider32*/33getStoredModelConfigs(providerName: string): Promise<Record<string, StoredModelConfig>>;3435/**36* Save model configuration to storage37*/38saveModelConfig(39modelId: string,40providerName: string,41config: {42apiKey: string;43deploymentUrl?: string;44modelCapabilities?: BYOKModelCapabilities;45},46authType: BYOKAuthType47): Promise<void>;48/**49* Handles the cases50* 1. Non custom model, and isDeletingCustomModel = false -> Delete from storage as we have the known model list51* 2. Custom model, and isDeletingCustomModel = true -> Delete from storage as we have the known model list52* 3. Custom model, and isDeletingCustomModel = false -> Do not delete from storage as we do not have the known model list. Instead mark unregistered53*/54removeModelConfig(modelId: string, providerName: string, isDeletingCustomModel: boolean): Promise<void>;55}5657export class BYOKStorageService implements IBYOKStorageService {58private readonly _extensionContext: IVSCodeExtensionContext;5960constructor(extensionContext: IVSCodeExtensionContext) {61this._extensionContext = extensionContext;62}6364public async getAPIKey(providerName: string, modelId?: string): Promise<string | undefined> {65// If model-specific key is requested, try to get it first66if (modelId) {67const modelKey = await this._extensionContext.secrets.get(`copilot-byok-${providerName}-${modelId}-api-key`);68// Only return the key if it's non-empty after trimming, and return the trimmed version69if (modelKey && modelKey.trim()) {70return modelKey.trim();71}72}7374// Fall back to provider key if no model-specific key or it was requested directly75const providerKey = await this._extensionContext.secrets.get(`copilot-byok-${providerName}-api-key`);76// Only return the key if it's non-empty after trimming, and return the trimmed version77return providerKey?.trim() || undefined;78}7980public async storeAPIKey(providerName: string, apiKey: string, authType: BYOKAuthType, modelId?: string): Promise<void> {81// Store API keys based on the provider's auth type82if (authType === BYOKAuthType.None) {83// Don't store keys for None auth type providers84return;85}8687// Ignore empty or whitespace-only API keys.88// This prevents invalid keys from being stored89if (!apiKey?.trim()) {90return;91}9293if (authType === BYOKAuthType.GlobalApiKey) {94// For GlobalApiKey providers, only store at provider level95await this._extensionContext.secrets.store(`copilot-byok-${providerName}-api-key`, apiKey);96} else if (authType === BYOKAuthType.PerModelDeployment && modelId) {97// For PerModelDeployment providers, store per model98await this._extensionContext.secrets.store(`copilot-byok-${providerName}-${modelId}-api-key`, apiKey);99}100}101102public async deleteAPIKey(providerName: string, authType: BYOKAuthType, modelId?: string): Promise<void> {103// Delete API keys based on the provider's auth type104if (authType === BYOKAuthType.None) {105// Nothing to delete for None auth type providers106return;107} else if (authType === BYOKAuthType.GlobalApiKey) {108// For GlobalApiKey providers, delete at provider level109await this._extensionContext.secrets.delete(`copilot-byok-${providerName}-api-key`);110} else if (authType === BYOKAuthType.PerModelDeployment && modelId) {111// For PerModelDeployment providers, delete per model112await this._extensionContext.secrets.delete(`copilot-byok-${providerName}-${modelId}-api-key`);113}114}115116public async getStoredModelConfigs(providerName: string): Promise<Record<string, StoredModelConfig>> {117return this._extensionContext.globalState.get<Record<string, StoredModelConfig>>(118`copilot-byok-${providerName}-models-config`,119{}120);121}122123public async saveModelConfig(124modelId: string,125providerName: string,126config: {127apiKey: string;128isCustomModel: boolean;129deploymentUrl?: string;130modelCapabilities?: BYOKModelCapabilities;131},132authType: BYOKAuthType133): Promise<void> {134// Save model configuration data135const configToSave: StoredModelConfig = {136isCustomModel: config.isCustomModel,137deploymentUrl: config.deploymentUrl,138isRegistered: true,139modelCapabilities: config.modelCapabilities140};141const existingConfigs = await this.getStoredModelConfigs(providerName);142existingConfigs[modelId] = configToSave;143await this._extensionContext.globalState.update(`copilot-byok-${providerName}-models-config`, existingConfigs);144145await this.storeAPIKey(providerName, config.apiKey, authType, modelId);146}147148public async removeModelConfig(modelId: string, providerName: string, isDeletingCustomModel: boolean): Promise<void> {149const existingConfigs = await this.getStoredModelConfigs(providerName);150const existingConfig = existingConfigs[modelId];151const isCustomModel = existingConfig?.isCustomModel || false;152if (existingConfig && (isDeletingCustomModel || !isCustomModel)) {153delete existingConfigs[modelId];154await this._extensionContext.globalState.update(155`copilot-byok-${providerName}-models-config`,156existingConfigs157);158// Remove API key from secrets159await this._extensionContext.secrets.delete(`copilot-byok-${providerName}-${modelId}-api-key`);160} else {161existingConfig.isRegistered = false;162await this._extensionContext.globalState.update(163`copilot-byok-${providerName}-models-config`,164existingConfigs165);166}167}168}169170