diff --git a/extensions/void/package-lock.json b/extensions/void/package-lock.json index 3c43f870..6d1d7f15 100644 --- a/extensions/void/package-lock.json +++ b/extensions/void/package-lock.json @@ -20,7 +20,7 @@ "@types/node": "^22.5.1", "@types/react": "^18.3.4", "@types/react-dom": "^18.3.0", - "@types/vscode": "1.92.0", + "@types/vscode": "^1.92.0", "@typescript-eslint/eslint-plugin": "^8.3.0", "@typescript-eslint/parser": "^8.3.0", "@vscode/test-cli": "^0.0.10", @@ -761,9 +761,9 @@ "license": "MIT" }, "node_modules/@types/vscode": { - "version": "1.92.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.92.0.tgz", - "integrity": "sha512-DcZoCj17RXlzB4XJ7IfKdPTcTGDLYvTOcTNkvtjXWF+K2TlKzHHkBEXNWQRpBIXixNEUgx39cQeTFunY0E2msw==", + "version": "1.94.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.94.0.tgz", + "integrity": "sha512-UyQOIUT0pb14XSqJskYnRwD2aG0QrPVefIfrW1djR+/J4KeFQ0i1+hjZoaAmeNf3Z2jleK+R2hv+EboG/m8ruw==", "dev": true, "license": "MIT" }, diff --git a/extensions/void/package.json b/extensions/void/package.json index 041116cf..fe99df52 100644 --- a/extensions/void/package.json +++ b/extensions/void/package.json @@ -25,7 +25,8 @@ "anthropic", "azure", "greptile", - "ollama" + "ollama", + "openaicompatible" ] }, "void.anthropic.apiKey": { @@ -191,6 +192,21 @@ "smollm:360m", "smollm:1.7b" ] + }, + "void.openaiCompatible.endpoint": { + "type": "string", + "default": "http://127.0.0.1:11434/v1", + "description": "The openai compatible api provider's endpoint. default value is a example of the ollama openai-mode uri" + }, + "void.openaiCompatible.model": { + "type": "string", + "default": "", + "description": "Your provider's model name to use." + }, + "void.openaiCompatible.apiKey": { + "type": "string", + "default": "", + "description": "Your provider's API Key." } } }, @@ -272,6 +288,7 @@ "@types/node": "^22.5.1", "@types/react": "^18.3.4", "@types/react-dom": "^18.3.0", + "@types/vscode": "^1.92.0", "@typescript-eslint/eslint-plugin": "^8.3.0", "@typescript-eslint/parser": "^8.3.0", "@vscode/test-cli": "^0.0.10", diff --git a/extensions/void/src/SidebarWebviewProvider.ts b/extensions/void/src/SidebarWebviewProvider.ts index e7baa51c..1173fa1e 100644 --- a/extensions/void/src/SidebarWebviewProvider.ts +++ b/extensions/void/src/SidebarWebviewProvider.ts @@ -44,6 +44,9 @@ export class SidebarWebviewProvider implements vscode.WebviewViewProvider { const ollamaEndpoint: string | undefined = vscode.workspace.getConfiguration('void').get('ollama.endpoint'); if (ollamaEndpoint) allowed_urls.push(ollamaEndpoint); + const openaiCompatibleEndpoint: string | undefined = vscode.workspace.getConfiguration('void').get('openaiCompatible.endpoint'); + if (openaiCompatibleEndpoint) + allowed_urls.push(openaiCompatibleEndpoint); const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'dist/sidebar/index.js')); const stylesUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'dist/sidebar/styles.css')); diff --git a/extensions/void/src/common/sendLLMMessage.ts b/extensions/void/src/common/sendLLMMessage.ts index 8f651de2..d49f39d9 100644 --- a/extensions/void/src/common/sendLLMMessage.ts +++ b/extensions/void/src/common/sendLLMMessage.ts @@ -28,6 +28,11 @@ export type ApiConfig = { endpoint: string, model: string }, + openaicompatible: { + endpoint: string, + model: string, + apikey: string + } whichApi: string } @@ -146,6 +151,47 @@ const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinal return { abort }; }; +// OpenAI Compatible +const sendOpenAICompatibleMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, apiConfig }) => { + + let didAbort = false + let fullText = '' + + // if abort is called, onFinalMessage is NOT called, and no later onTexts are called either + let abort: () => void = () => { + didAbort = true; + }; + + const openai = new OpenAI({ apiKey: apiConfig.openaicompatible.apikey, baseURL: apiConfig.openaicompatible.endpoint, dangerouslyAllowBrowser: true }); + + openai.chat.completions.create({ + model: apiConfig.openaicompatible.model, + messages: messages, + stream: true, + }) + .then(async response => { + abort = () => { + // response.controller.abort() + didAbort = true; + } + // when receive text + try { + for await (const chunk of response) { + if (didAbort) return; + const newText = chunk.choices[0]?.delta?.content || ''; + fullText += newText; + onText(newText, fullText); + } + onFinalMessage(fullText); + } + // when error/fail + catch (error) { + console.error('Error in OpenAI stream:', error); + onFinalMessage(fullText); + } + }) + return { abort }; +}; // Ollama @@ -277,6 +323,8 @@ export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({ messages, onText, return sendGreptileMsg({ messages, onText, onFinalMessage, apiConfig }); case 'ollama': return sendOllamaMsg({ messages, onText, onFinalMessage, apiConfig }); + case 'openaicompatible': + return sendOpenAICompatibleMsg({ messages, onText, onFinalMessage, apiConfig }); default: console.error(`Error: whichApi was ${apiConfig.whichApi}, which is not recognized!`); return { abort: () => { } } diff --git a/extensions/void/src/extension.ts b/extensions/void/src/extension.ts index 20630248..262953ec 100644 --- a/extensions/void/src/extension.ts +++ b/extensions/void/src/extension.ts @@ -35,6 +35,11 @@ const getApiConfig = () => { endpoint: vscode.workspace.getConfiguration('void.ollama').get('endpoint') ?? '', model: vscode.workspace.getConfiguration('void.ollama').get('model') ?? '', }, + openaicompatible: { + endpoint: vscode.workspace.getConfiguration('void.openaiCompatible').get('endpoint') ?? '', + apikey: vscode.workspace.getConfiguration('void.openaiCompatible').get('apiKey') ?? '', + model: vscode.workspace.getConfiguration('void.openaiCompatible').get('model') ?? '', + }, whichApi: vscode.workspace.getConfiguration('void').get('whichApi') ?? '' } return apiConfig