Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/byok/vscode-node/byokStorageService.ts
13399 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
import { IVSCodeExtensionContext } from '../../../platform/extContext/common/extensionContext';
6
import { BYOKAuthType, BYOKModelCapabilities } from '../../byok/common/byokProvider';
7
8
export interface StoredModelConfig {
9
deploymentUrl?: string;
10
isRegistered?: boolean; // Will be undefined for now but eventually storage will update to be true / false.
11
isCustomModel?: boolean; // Will be undefined for now but eventually storage will update to be true / false.
12
modelCapabilities?: BYOKModelCapabilities;
13
}
14
15
export interface IBYOKStorageService {
16
/**
17
* Get API key for a provider or model
18
*/
19
getAPIKey(providerName: string, modelId?: string): Promise<string | undefined>;
20
21
/**
22
* Store API key for a provider or model based on auth type
23
*/
24
storeAPIKey(providerName: string, apiKey: string, authType: BYOKAuthType, modelId?: string): Promise<void>;
25
26
/**
27
* Delete API key for a provider or model based on auth type
28
*/
29
deleteAPIKey(providerName: string, authType: BYOKAuthType, modelId?: string): Promise<void>;
30
31
/**
32
* Get all stored model configurations for a provider
33
*/
34
getStoredModelConfigs(providerName: string): Promise<Record<string, StoredModelConfig>>;
35
36
/**
37
* Save model configuration to storage
38
*/
39
saveModelConfig(
40
modelId: string,
41
providerName: string,
42
config: {
43
apiKey: string;
44
deploymentUrl?: string;
45
modelCapabilities?: BYOKModelCapabilities;
46
},
47
authType: BYOKAuthType
48
): Promise<void>;
49
/**
50
* Handles the cases
51
* 1. Non custom model, and isDeletingCustomModel = false -> Delete from storage as we have the known model list
52
* 2. Custom model, and isDeletingCustomModel = true -> Delete from storage as we have the known model list
53
* 3. Custom model, and isDeletingCustomModel = false -> Do not delete from storage as we do not have the known model list. Instead mark unregistered
54
*/
55
removeModelConfig(modelId: string, providerName: string, isDeletingCustomModel: boolean): Promise<void>;
56
}
57
58
export class BYOKStorageService implements IBYOKStorageService {
59
private readonly _extensionContext: IVSCodeExtensionContext;
60
61
constructor(extensionContext: IVSCodeExtensionContext) {
62
this._extensionContext = extensionContext;
63
}
64
65
public async getAPIKey(providerName: string, modelId?: string): Promise<string | undefined> {
66
// If model-specific key is requested, try to get it first
67
if (modelId) {
68
const modelKey = await this._extensionContext.secrets.get(`copilot-byok-${providerName}-${modelId}-api-key`);
69
// Only return the key if it's non-empty after trimming, and return the trimmed version
70
if (modelKey && modelKey.trim()) {
71
return modelKey.trim();
72
}
73
}
74
75
// Fall back to provider key if no model-specific key or it was requested directly
76
const providerKey = await this._extensionContext.secrets.get(`copilot-byok-${providerName}-api-key`);
77
// Only return the key if it's non-empty after trimming, and return the trimmed version
78
return providerKey?.trim() || undefined;
79
}
80
81
public async storeAPIKey(providerName: string, apiKey: string, authType: BYOKAuthType, modelId?: string): Promise<void> {
82
// Store API keys based on the provider's auth type
83
if (authType === BYOKAuthType.None) {
84
// Don't store keys for None auth type providers
85
return;
86
}
87
88
// Ignore empty or whitespace-only API keys.
89
// This prevents invalid keys from being stored
90
if (!apiKey?.trim()) {
91
return;
92
}
93
94
if (authType === BYOKAuthType.GlobalApiKey) {
95
// For GlobalApiKey providers, only store at provider level
96
await this._extensionContext.secrets.store(`copilot-byok-${providerName}-api-key`, apiKey);
97
} else if (authType === BYOKAuthType.PerModelDeployment && modelId) {
98
// For PerModelDeployment providers, store per model
99
await this._extensionContext.secrets.store(`copilot-byok-${providerName}-${modelId}-api-key`, apiKey);
100
}
101
}
102
103
public async deleteAPIKey(providerName: string, authType: BYOKAuthType, modelId?: string): Promise<void> {
104
// Delete API keys based on the provider's auth type
105
if (authType === BYOKAuthType.None) {
106
// Nothing to delete for None auth type providers
107
return;
108
} else if (authType === BYOKAuthType.GlobalApiKey) {
109
// For GlobalApiKey providers, delete at provider level
110
await this._extensionContext.secrets.delete(`copilot-byok-${providerName}-api-key`);
111
} else if (authType === BYOKAuthType.PerModelDeployment && modelId) {
112
// For PerModelDeployment providers, delete per model
113
await this._extensionContext.secrets.delete(`copilot-byok-${providerName}-${modelId}-api-key`);
114
}
115
}
116
117
public async getStoredModelConfigs(providerName: string): Promise<Record<string, StoredModelConfig>> {
118
return this._extensionContext.globalState.get<Record<string, StoredModelConfig>>(
119
`copilot-byok-${providerName}-models-config`,
120
{}
121
);
122
}
123
124
public async saveModelConfig(
125
modelId: string,
126
providerName: string,
127
config: {
128
apiKey: string;
129
isCustomModel: boolean;
130
deploymentUrl?: string;
131
modelCapabilities?: BYOKModelCapabilities;
132
},
133
authType: BYOKAuthType
134
): Promise<void> {
135
// Save model configuration data
136
const configToSave: StoredModelConfig = {
137
isCustomModel: config.isCustomModel,
138
deploymentUrl: config.deploymentUrl,
139
isRegistered: true,
140
modelCapabilities: config.modelCapabilities
141
};
142
const existingConfigs = await this.getStoredModelConfigs(providerName);
143
existingConfigs[modelId] = configToSave;
144
await this._extensionContext.globalState.update(`copilot-byok-${providerName}-models-config`, existingConfigs);
145
146
await this.storeAPIKey(providerName, config.apiKey, authType, modelId);
147
}
148
149
public async removeModelConfig(modelId: string, providerName: string, isDeletingCustomModel: boolean): Promise<void> {
150
const existingConfigs = await this.getStoredModelConfigs(providerName);
151
const existingConfig = existingConfigs[modelId];
152
const isCustomModel = existingConfig?.isCustomModel || false;
153
if (existingConfig && (isDeletingCustomModel || !isCustomModel)) {
154
delete existingConfigs[modelId];
155
await this._extensionContext.globalState.update(
156
`copilot-byok-${providerName}-models-config`,
157
existingConfigs
158
);
159
// Remove API key from secrets
160
await this._extensionContext.secrets.delete(`copilot-byok-${providerName}-${modelId}-api-key`);
161
} else {
162
existingConfig.isRegistered = false;
163
await this._extensionContext.globalState.update(
164
`copilot-byok-${providerName}-models-config`,
165
existingConfigs
166
);
167
}
168
}
169
}
170