diff --git a/package.json b/package.json index 99b1c22..37690e4 100644 --- a/package.json +++ b/package.json @@ -107,10 +107,15 @@ "description": "The max number of tokens of a prompt.", "when": "DevChat.llmModel == 'OpenAI'" }, - "DevChat.API_KEY": { + "DevChat.Access_Key_DevChat": { "type": "string", "default": "", - "description": "API key for accessing the LLM model", + "description": "DevChat's secret key for accessing multiple LLM models" + }, + "DevChat.Api_Key_OpenAI": { + "type": "string", + "default": "", + "description": "OpenAI's secret key for accessing LLM models. (Leave blank if using DevChat's key.)", "when": "DevChat.llmModel == 'OpenAI'" }, "DevChat.API_ENDPOINT": { @@ -205,8 +210,13 @@ "title": "Create Entry" }, { - "command": "DevChat.OPENAI_API_KEY", - "title": "Input Access Key", + "command": "DevChat.Api_Key_OpenAI", + "title": "Input OpenAI Api Key", + "category": "DevChat" + }, + { + "command": "DevChat.Access_Key_DevChat", + "title": "Input DevChat Access Key", "category": "DevChat" }, { diff --git a/src/contributes/commands.ts b/src/contributes/commands.ts index 5845d20..c98e9a2 100644 --- a/src/contributes/commands.ts +++ b/src/contributes/commands.ts @@ -64,21 +64,40 @@ function registerAskForFileCommand(context: vscode.ExtensionContext) { context.subscriptions.push(vscode.commands.registerCommand('devchat.askForFile_chinese', callback)); } -export function registerApiKeySettingCommand(context: vscode.ExtensionContext) { +export function registerOpenAiApiKeySettingCommand(context: vscode.ExtensionContext) { const secretStorage: vscode.SecretStorage = context.secrets; context.subscriptions.push( - vscode.commands.registerCommand('DevChat.OPENAI_API_KEY', async () => { + vscode.commands.registerCommand('DevChat.Api_Key_OpenAI', async () => { const passwordInput: string = await vscode.window.showInputBox({ password: true, - title: "Input Access Key", - placeHolder: "Set OPENAI_API_KEY (or DevChat Access Key)" + title: "Input OpenAi Api Key", + placeHolder: "Set OpenAI Api Key.(Leave blank if clearing stored key.)" + }) ?? ''; + + if (passwordInput.trim() !== "" && !isValidApiKey(passwordInput)) { + UiUtilWrapper.showErrorMessage("Your api key is invalid!"); + return ; + } + ApiKeyManager.writeApiKeySecret(passwordInput, "OpenAI"); + }) + ); +} + +export function registerDevChatApiKeySettingCommand(context: vscode.ExtensionContext) { + const secretStorage: vscode.SecretStorage = context.secrets; + context.subscriptions.push( + vscode.commands.registerCommand('DevChat.Access_Key_DevChat', async () => { + const passwordInput: string = await vscode.window.showInputBox({ + password: true, + title: "Input DevChat Access Key", + placeHolder: "Set DevChat Access Key.(Leave blank if clearing stored key.)" }) ?? ''; if (passwordInput.trim() !== "" && !isValidApiKey(passwordInput)) { UiUtilWrapper.showErrorMessage("Your access key is invalid!"); return ; } - ApiKeyManager.writeApiKeySecret(passwordInput); + ApiKeyManager.writeApiKeySecret(passwordInput, "DevChat"); }) ); } diff --git a/src/extension.ts b/src/extension.ts index cb6279c..b1a4eb6 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -5,7 +5,8 @@ import { registerAddContextCommand, registerAskForCodeCommand, registerAskForFileCommand, - registerApiKeySettingCommand, + registerOpenAiApiKeySettingCommand, + registerDevChatApiKeySettingCommand, regTopicDeleteCommand, regAddTopicCommand, regDeleteSelectTopicCommand, @@ -37,7 +38,8 @@ function activate(context: vscode.ExtensionContext) { regDevChatView(context); regTopicView(context); - registerApiKeySettingCommand(context); + registerOpenAiApiKeySettingCommand(context); + registerDevChatApiKeySettingCommand(context); registerOpenChatPanelCommand(context); registerAddContextCommand(context); registerAskForCodeCommand(context); diff --git a/src/handler/historyMessagesBase.ts b/src/handler/historyMessagesBase.ts index 9972efc..2cbbe98 100644 --- a/src/handler/historyMessagesBase.ts +++ b/src/handler/historyMessagesBase.ts @@ -45,9 +45,22 @@ OPENAI_API_KEY is missing from your environment or settings. Kindly input your O } as LogEntry; } -export function isValidApiKey(apiKey: string) { +export function isValidApiKey(apiKey: string, llmType: string = "None") { let apiKeyStrim = apiKey.trim(); - if (ApiKeyManager.getKeyType(apiKeyStrim) === undefined) { + const apiKeyType = ApiKeyManager.getKeyType(apiKeyStrim); + if (apiKeyType === undefined) { + return false; + } + if (llmType === "OpenAI") { + if (apiKeyType === "sk") { + return true; + } + return false; + } + if (llmType === "DevChat") { + if (apiKeyType === "DC") { + return true; + } return false; } return true; diff --git a/src/panel/statusBarView.ts b/src/panel/statusBarView.ts index ea1825a..6347ef5 100644 --- a/src/panel/statusBarView.ts +++ b/src/panel/statusBarView.ts @@ -45,7 +45,7 @@ export function createStatusBarItem(context: vscode.ExtensionContext): vscode.St if (apiKeyStatus !== 'ready') { statusBarItem.text = `$(warning)DevChat`; statusBarItem.tooltip = `${apiKeyStatus}`; - statusBarItem.command = 'DevChat.OPENAI_API_KEY'; + statusBarItem.command = 'DevChat.Access_Key_DevChat'; return; } diff --git a/src/util/apiKey.ts b/src/util/apiKey.ts index c5e0202..4a95592 100644 --- a/src/util/apiKey.ts +++ b/src/util/apiKey.ts @@ -3,13 +3,28 @@ import { UiUtilWrapper } from './uiUtil'; export class ApiKeyManager { - static async getApiKey(): Promise { - let apiKey = await UiUtilWrapper.secretStorageGet("devchat_OPENAI_API_KEY"); + static async getApiKey(llmType: string = "OpenAI"): Promise { + let apiKey: string|undefined = undefined; + + if (llmType === "OpenAI") { + apiKey = await UiUtilWrapper.secretStorageGet("openai_OPENAI_API_KEY"); + } if (!apiKey) { - apiKey = UiUtilWrapper.getConfiguration('DevChat', 'API_KEY'); + apiKey = await UiUtilWrapper.secretStorageGet("devchat_OPENAI_API_KEY"); + } + + if (!apiKey) { + if (llmType === "OpenAI") { + apiKey = UiUtilWrapper.getConfiguration('DevChat', 'Api_Key_OpenAI'); + } + if (!apiKey) { + apiKey = UiUtilWrapper.getConfiguration('DevChat', 'Access_Key_DevChat'); + } } if (!apiKey) { - apiKey = process.env.OPENAI_API_KEY; + if (llmType === "OpenAI") { + apiKey = process.env.OPENAI_API_KEY; + } } return apiKey; } @@ -24,8 +39,18 @@ export class ApiKeyManager { } } - static async writeApiKeySecret(apiKey: string): Promise { - await UiUtilWrapper.storeSecret("devchat_OPENAI_API_KEY", apiKey); + static async writeApiKeySecret(apiKey: string, llmType: string = "Unknow"): Promise { + if (apiKey.startsWith("sk-")) { + await UiUtilWrapper.storeSecret("openai_OPENAI_API_KEY", apiKey); + } else if (apiKey.startsWith("DC.")) { + await UiUtilWrapper.storeSecret("devchat_OPENAI_API_KEY", apiKey); + } else { + if (llmType === "OpenAI") { + await UiUtilWrapper.storeSecret("openai_OPENAI_API_KEY", apiKey); + } else if (llmType === "DevChat") { + await UiUtilWrapper.storeSecret("devchat_OPENAI_API_KEY", apiKey); + } + } } static getEndPoint(apiKey: string | undefined): string | undefined { diff --git a/test/handler/sendMessageBase.test.ts b/test/handler/sendMessageBase.test.ts index a71a7da..6e1c2fb 100644 --- a/test/handler/sendMessageBase.test.ts +++ b/test/handler/sendMessageBase.test.ts @@ -129,7 +129,7 @@ describe('sendMessageBase', () => { workspaceFoldersFirstPathStub.returns('./'); - getConfigurationStub.withArgs('DevChat', 'API_KEY').returns(process.env.TEST_DEVCHAT_KEY); + getConfigurationStub.withArgs('DevChat', 'Access_Key_DevChat').returns(process.env.TEST_DEVCHAT_KEY); getConfigurationStub.withArgs('DevChat', 'OpenAI.model').returns('gpt-4'); getConfigurationStub.withArgs('DevChat', 'OpenAI.temperature').returns(0); getConfigurationStub.withArgs('DevChat', 'OpenAI.stream').returns('true'); @@ -156,7 +156,7 @@ describe('sendMessageBase', () => { workspaceFoldersFirstPathStub.returns('./'); - getConfigurationStub.withArgs('DevChat', 'API_KEY').returns('sk-KvH7ZCtHmFDCBTqH0jUv'); + getConfigurationStub.withArgs('DevChat', 'Access_Key_DevChat').returns('sk-KvH7ZCtHmFDCBTqH0jUv'); getConfigurationStub.withArgs('DevChat', 'OpenAI.model').returns('gpt-4'); getConfigurationStub.withArgs('DevChat', 'OpenAI.temperature').returns('0'); getConfigurationStub.withArgs('DevChat', 'OpenAI.stream').returns('true'); @@ -185,7 +185,7 @@ describe('sendMessageBase', () => { workspaceFoldersFirstPathStub.returns('./'); - getConfigurationStub.withArgs('DevChat', 'API_KEY').returns(process.env.TEST_DEVCHAT_KEY); + getConfigurationStub.withArgs('DevChat', 'Access_Key_DevChat').returns(process.env.TEST_DEVCHAT_KEY); getConfigurationStub.withArgs('DevChat', 'OpenAI.model').returns('gpt-4'); getConfigurationStub.withArgs('DevChat', 'OpenAI.temperature').returns(0); getConfigurationStub.withArgs('DevChat', 'OpenAI.stream').returns('true'); diff --git a/test/util/apiKey.test.ts b/test/util/apiKey.test.ts index b0db0d1..1af634b 100644 --- a/test/util/apiKey.test.ts +++ b/test/util/apiKey.test.ts @@ -85,7 +85,7 @@ describe('ApiKeyManager', () => { const storeSecretStub = sinon.stub(UiUtilWrapper, 'storeSecret').resolves(); await ApiKeyManager.writeApiKeySecret('sk-secret'); - expect(storeSecretStub.calledWith('devchat_OPENAI_API_KEY', 'sk-secret')).to.be.true; + expect(storeSecretStub.calledWith('openai_OPENAI_API_KEY', 'sk-secret')).to.be.true; }); }); }); \ No newline at end of file