From f4ceb53738250b194c3231579eb8f826b57d366a Mon Sep 17 00:00:00 2001 From: w1gs Date: Wed, 18 Sep 2024 02:02:43 -0400 Subject: [PATCH 01/67] Added Ollama integration --- extensions/void/package.json | 10 +- extensions/void/src/SidebarWebviewProvider.ts | 5 +- extensions/void/src/common/sendLLMMessage.ts | 124 ++++++++++-------- extensions/void/src/extension.ts | 3 +- 4 files changed, 83 insertions(+), 59 deletions(-) diff --git a/extensions/void/package.json b/extensions/void/package.json index ebccfe72..21be74ed 100644 --- a/extensions/void/package.json +++ b/extensions/void/package.json @@ -40,10 +40,15 @@ "default": "", "description": "Greptile - Github PAT (gives Greptile access to your repo)" }, - "void.ollamaSettings": { + "void.ollamaSettings.endpoint": { "type": "string", "default": "", - "description": "Ollama settings (coming soon...)" + "description": "Ollama Endpoint - Local API server can be started with `OLLAMA_ORIGINS=* ollama serve`" + }, + "void.ollamaSettings.model": { + "type": "string", + "default": "", + "description": "Ollama model to use" } } }, @@ -126,7 +131,6 @@ "eslint-plugin-react-hooks": "^4.6.2", "globals": "^15.9.0", "marked": "^14.1.0", - "ollama": "^0.5.8", "postcss": "^8.4.41", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/extensions/void/src/SidebarWebviewProvider.ts b/extensions/void/src/SidebarWebviewProvider.ts index fe01c800..46c45085 100644 --- a/extensions/void/src/SidebarWebviewProvider.ts +++ b/extensions/void/src/SidebarWebviewProvider.ts @@ -54,8 +54,9 @@ export class SidebarWebviewProvider implements vscode.WebviewViewProvider { const nonce = getNonce(); // only scripts with the nonce are allowed to run, this is a recommended security measure - - const allowed_urls = ['https://api.anthropic.com', 'https://api.openai.com', 'https://api.greptile.com'] + // Allow Ollama endpoint + const ollamaEndpoint = vscode.workspace.getConfiguration('void').get('ollamaSettings.endpoint') || 'http://localhost:11434' + const allowed_urls = ['https://api.anthropic.com', 'https://api.openai.com', 'https://api.greptile.com', ollamaEndpoint ] webview.html = ` diff --git a/extensions/void/src/common/sendLLMMessage.ts b/extensions/void/src/common/sendLLMMessage.ts index ca3c34bd..bf87f40b 100644 --- a/extensions/void/src/common/sendLLMMessage.ts +++ b/extensions/void/src/common/sendLLMMessage.ts @@ -1,8 +1,6 @@ import Anthropic from '@anthropic-ai/sdk'; import OpenAI from 'openai'; -// import ollama from 'ollama' - export type ApiConfig = { anthropic: { apikey: string, @@ -20,7 +18,8 @@ export type ApiConfig = { } }, ollama: { - // TODO + endpoint: string, + model: string }, whichApi: string } @@ -220,66 +219,85 @@ const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFin export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({ messages, onText, onFinalMessage, apiConfig }) => { if (!apiConfig) return { abort: () => { } } - const whichApi = apiConfig.whichApi + const whichApi = apiConfig.whichApi; - if (whichApi === 'anthropic') { - return sendClaudeMsg({ messages, onText, onFinalMessage, apiConfig }) + switch (whichApi) { + case 'anthropic': + return sendClaudeMsg({ messages, onText, onFinalMessage, apiConfig }); + case 'openai': + return sendOpenAIMsg({ messages, onText, onFinalMessage, apiConfig }); + case 'greptile': + return sendGreptileMsg({ messages, onText, onFinalMessage, apiConfig }); + case 'ollama': + return sendOllamaMsg({ messages, onText, onFinalMessage, apiConfig }); + default: + console.error(`Error: whichApi was ${whichApi}, which is not recognized!`); + return sendClaudeMsg({ messages, onText, onFinalMessage, apiConfig }); // TODO } - else if (whichApi === 'openai') { - return sendOpenAIMsg({ messages, onText, onFinalMessage, apiConfig }) - } - else if (whichApi === 'greptile') { - return sendGreptileMsg({ messages, onText, onFinalMessage, apiConfig }) - } - else if (whichApi === 'ollama') { - return sendClaudeMsg({ messages, onText, onFinalMessage, apiConfig }) // TODO - } - else { - console.error(`Error: whichApi was ${whichApi}, which is not recognized!`) - return sendClaudeMsg({ messages, onText, onFinalMessage, apiConfig }) // TODO - } - } // Ollama -// const sendOllamaMsg: sendMsgFnType = ({ messages, onText, onFinalMessage }) => { +export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, apiConfig }) => { + let didAbort = false; + let fullText = ""; -// let did_abort = false -// let fullText = '' + // if abort is called, onFinalMessage is NOT called, and no later onTexts are called either + const abort = () => { + didAbort = true; + }; -// // if abort is called, onFinalMessage is NOT called, and no later onTexts are called either -// let abort: () => void = () => { -// did_abort = true -// } + const handleError = (error: any) => { + console.error('Error:', error); + onFinalMessage(fullText); + }; -// ollama.chat({ model: 'llama3.1', messages: messages, stream: true }) -// .then(async response => { + if (apiConfig.ollama.endpoint.endsWith('/')) { + apiConfig.ollama.endpoint = apiConfig.ollama.endpoint.slice(0, -1); + } -// abort = () => { -// // response.abort() // this isn't needed now, to keep consistency with claude will leave it commented for now -// did_abort = true; -// } + fetch(`${apiConfig.ollama.endpoint}/api/chat`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + model: apiConfig.ollama.model, + messages: messages, + stream: true, + }), + }) + .then(response => { + if (didAbort) return; + const reader = response.body?.getReader(); + if (!reader) { + onFinalMessage(fullText); + return; + } + return reader; + }) + .then(reader => { + if (!reader) return; -// // when receive text -// try { -// for await (const part of response) { -// if (did_abort) return -// let newText = part.message.content -// fullText += newText -// onText(newText, fullText) -// } -// } -// // when error/fail -// catch (e) { -// onFinalMessage(fullText) -// return -// } + const readStream = async () => { + try { + let done, value; + while ({ done, value } = await reader.read(), !done) { + if (didAbort) return; + const stringedResponse = new TextDecoder().decode(value); + const newText = JSON.parse(stringedResponse).message.content; + fullText += newText; + onText(newText, fullText); + } + onFinalMessage(fullText); + } catch (error) { + handleError(error); + } + }; -// // when we get the final message on this stream -// onFinalMessage(fullText) -// }) - -// return { abort }; -// }; + readStream(); + }) + .catch(handleError); + return { abort }; +}; diff --git a/extensions/void/src/extension.ts b/extensions/void/src/extension.ts index 4cd8ac26..d95c2873 100644 --- a/extensions/void/src/extension.ts +++ b/extensions/void/src/extension.ts @@ -25,7 +25,8 @@ const getApiConfig = () => { } }, ollama: { - // apikey: vscode.workspace.getConfiguration('void').get('ollamaSettings') ?? '', + endpoint: vscode.workspace.getConfiguration('void').get('ollamaSettings.endpoint') ?? '', + model: vscode.workspace.getConfiguration('void').get('ollamaSettings.model') ?? '', }, whichApi: vscode.workspace.getConfiguration('void').get('whichApi') ?? '' } From 6d5f8853062265545bc4701f8058a7836f23af46 Mon Sep 17 00:00:00 2001 From: Jelf Date: Fri, 20 Sep 2024 22:18:41 +0800 Subject: [PATCH 02/67] feat: ai sdk --- extensions/void/package-lock.json | 509 +++++++++++++++++++ extensions/void/package.json | 5 + extensions/void/src/common/sendLLMMessage.ts | 227 +++------ 3 files changed, 575 insertions(+), 166 deletions(-) diff --git a/extensions/void/package-lock.json b/extensions/void/package-lock.json index cb1b8f87..f2b84f0f 100644 --- a/extensions/void/package-lock.json +++ b/extensions/void/package-lock.json @@ -8,7 +8,12 @@ "name": "void", "version": "0.0.1", "dependencies": { + "@ai-sdk/anthropic": "^0.0.50", + "@ai-sdk/azure": "^0.0.39", + "@ai-sdk/openai": "^0.0.60", "@anthropic-ai/sdk": "^0.27.1", + "ai": "^3.3.43", + "ollama-ai-provider": "^0.15.0", "openai": "^4.57.0" }, "devDependencies": { @@ -46,6 +51,239 @@ "vscode": "^1.89.0" } }, + "node_modules/@ai-sdk/anthropic": { + "version": "0.0.50", + "resolved": "https://registry.npmmirror.com/@ai-sdk/anthropic/-/anthropic-0.0.50.tgz", + "integrity": "sha512-++mqmFcUoQgjoCchAU6eVG3QfKdwkeJVNdMZ+jUiNdawn8diA6BlARlu7xFT4F7W3bcStfYv4hK1jwRyzAQtCg==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "0.0.23", + "@ai-sdk/provider-utils": "1.0.19" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + } + }, + "node_modules/@ai-sdk/azure": { + "version": "0.0.39", + "resolved": "https://registry.npmmirror.com/@ai-sdk/azure/-/azure-0.0.39.tgz", + "integrity": "sha512-P6CdIwLfkvkhf2hHkbnhqLOkVMQLu7XlksJ2YvzS4yx2Iwl8fsKLVnaCWMX9SBlYfamT/oea+rpyF60JhkdMZg==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/openai": "0.0.61", + "@ai-sdk/provider": "0.0.23", + "@ai-sdk/provider-utils": "1.0.19" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + } + }, + "node_modules/@ai-sdk/azure/node_modules/@ai-sdk/openai": { + "version": "0.0.61", + "resolved": "https://registry.npmmirror.com/@ai-sdk/openai/-/openai-0.0.61.tgz", + "integrity": "sha512-yIJ70xU9sbDjVAaNoq+W+0jnAgIUsx4e9VTnoNPXNTIQRpgpLvQ7iG8GYNgujO4oX4sLiHsWpOEMzrSwD0mNmw==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "0.0.23", + "@ai-sdk/provider-utils": "1.0.19" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + } + }, + "node_modules/@ai-sdk/openai": { + "version": "0.0.60", + "resolved": "https://registry.npmmirror.com/@ai-sdk/openai/-/openai-0.0.60.tgz", + "integrity": "sha512-NEdDdv3o76jT6UeWHxP6I/lMYcjFQhQGQi/U2gVqW1PEU4Pjaud7tAVSy27IPbiRakg6GOzWrltI2JhZgAI1wg==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "0.0.23", + "@ai-sdk/provider-utils": "1.0.19" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + } + }, + "node_modules/@ai-sdk/provider": { + "version": "0.0.23", + "resolved": "https://registry.npmmirror.com/@ai-sdk/provider/-/provider-0.0.23.tgz", + "integrity": "sha512-oAc49O5+xypVrKM7EUU5P/Y4DUL4JZUWVxhejoAVOTOl3WZUEWsMbP3QZR+TrimQIsS0WR/n9UuF6U0jPdp0tQ==", + "license": "Apache-2.0", + "dependencies": { + "json-schema": "0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/provider-utils": { + "version": "1.0.19", + "resolved": "https://registry.npmmirror.com/@ai-sdk/provider-utils/-/provider-utils-1.0.19.tgz", + "integrity": "sha512-p02Fq5Mnc8T6nwRBN1Iaou8YXvN1sDS6hbmJaD5UaRbXjizbh+8rpFS/o7jqAHTwf3uHCDitP3pnODyHdc/CDQ==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "0.0.23", + "eventsource-parser": "1.1.2", + "nanoid": "3.3.6", + "secure-json-parse": "2.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@ai-sdk/provider-utils/node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/@ai-sdk/react": { + "version": "0.0.59", + "resolved": "https://registry.npmmirror.com/@ai-sdk/react/-/react-0.0.59.tgz", + "integrity": "sha512-1WbgO3J2/OoheMuNMxy5itJ3NVqOpqpAQxFNp7AoXgnDv4wDF4kTif61rTlKh7dCPvBHj2HXLmob+TrVFaWhYw==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider-utils": "1.0.19", + "@ai-sdk/ui-utils": "0.0.44", + "swr": "2.2.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^18 || ^19", + "zod": "^3.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/@ai-sdk/solid": { + "version": "0.0.47", + "resolved": "https://registry.npmmirror.com/@ai-sdk/solid/-/solid-0.0.47.tgz", + "integrity": "sha512-lVMxIxtuNqoo/TObSFGflEP2dUeJv7bfPQbS4jHTZGBNlyhgBRY2Xc19yNjA3QKRfvQNDVoQusqxn+18MiHJJQ==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider-utils": "1.0.19", + "@ai-sdk/ui-utils": "0.0.44" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "solid-js": "^1.7.7" + }, + "peerDependenciesMeta": { + "solid-js": { + "optional": true + } + } + }, + "node_modules/@ai-sdk/svelte": { + "version": "0.0.49", + "resolved": "https://registry.npmmirror.com/@ai-sdk/svelte/-/svelte-0.0.49.tgz", + "integrity": "sha512-gV0MhaWxkatjf7uJrCAHO3bWrihokNUwGhuMCgyG+y53lwJKAYhR0zCoDRM2HnTJ89fdnx/PVe3R9fOWEVY5qA==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider-utils": "1.0.19", + "@ai-sdk/ui-utils": "0.0.44", + "sswr": "2.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "svelte": "^3.0.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + } + } + }, + "node_modules/@ai-sdk/ui-utils": { + "version": "0.0.44", + "resolved": "https://registry.npmmirror.com/@ai-sdk/ui-utils/-/ui-utils-0.0.44.tgz", + "integrity": "sha512-0qiyun/n5zqJzQs/WfQT86dZE5DiDhSHJc7b7ZGLYvNMztHkRQmak2zUCZP4IyGVZEicyEPQK6NEEpBgkmd3Dg==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "0.0.23", + "@ai-sdk/provider-utils": "1.0.19", + "json-schema": "0.4.0", + "secure-json-parse": "2.7.0", + "zod-to-json-schema": "3.23.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@ai-sdk/vue": { + "version": "0.0.50", + "resolved": "https://registry.npmmirror.com/@ai-sdk/vue/-/vue-0.0.50.tgz", + "integrity": "sha512-eIWfxqpKwRdL3rxJMg1HDJcjfugFJGg4P934Tl69S7UCot2/U4BPZoESVJQFroS1elbKHaMRgv0ZJt1ddWQPjQ==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider-utils": "1.0.19", + "@ai-sdk/ui-utils": "0.0.44", + "swrv": "1.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "vue": "^3.3.4" + }, + "peerDependenciesMeta": { + "vue": { + "optional": true + } + } + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -533,6 +771,15 @@ "node": ">= 8" } }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -564,6 +811,12 @@ "integrity": "sha512-qVqLpd49rmJA2nZzLVsmfS/aiiBpfVE95dHhPVwG0NmSBAt+riPxnj53wq2oBq5m4Q2RF1IWFEUpnZTgrQZfEQ==", "dev": true }, + "node_modules/@types/diff-match-patch": { + "version": "1.0.36", + "resolved": "https://registry.npmmirror.com/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz", + "integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==", + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -1000,6 +1253,73 @@ "node": ">= 8.0.0" } }, + "node_modules/ai": { + "version": "3.3.43", + "resolved": "https://registry.npmmirror.com/ai/-/ai-3.3.43.tgz", + "integrity": "sha512-B4susrnVOapUVRYWuzNp3ChFZ7lBB3w9VFofIDiwrAoOkrljImkZgE2cUdIvWzwj/v3DiBtfPsPbOv+S2YRkCQ==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "0.0.23", + "@ai-sdk/provider-utils": "1.0.19", + "@ai-sdk/react": "0.0.59", + "@ai-sdk/solid": "0.0.47", + "@ai-sdk/svelte": "0.0.49", + "@ai-sdk/ui-utils": "0.0.44", + "@ai-sdk/vue": "0.0.50", + "@opentelemetry/api": "1.9.0", + "eventsource-parser": "1.1.2", + "json-schema": "0.4.0", + "jsondiffpatch": "0.6.0", + "nanoid": "3.3.6", + "secure-json-parse": "2.7.0", + "zod-to-json-schema": "3.23.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "openai": "^4.42.0", + "react": "^18 || ^19", + "sswr": "^2.1.0", + "svelte": "^3.0.0 || ^4.0.0", + "zod": "^3.0.0" + }, + "peerDependenciesMeta": { + "openai": { + "optional": true + }, + "react": { + "optional": true + }, + "sswr": { + "optional": true + }, + "svelte": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/ai/node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -1670,6 +1990,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -1997,6 +2323,12 @@ "node": ">=0.3.1" } }, + "node_modules/diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==", + "license": "Apache-2.0" + }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -2578,6 +2910,15 @@ "node": ">=6" } }, + "node_modules/eventsource-parser": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/eventsource-parser/-/eventsource-parser-1.1.2.tgz", + "integrity": "sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==", + "license": "MIT", + "engines": { + "node": ">=14.18" + } + }, "node_modules/expect": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", @@ -3957,6 +4298,12 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -3969,6 +4316,35 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "node_modules/jsondiffpatch": { + "version": "0.6.0", + "resolved": "https://registry.npmmirror.com/jsondiffpatch/-/jsondiffpatch-0.6.0.tgz", + "integrity": "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==", + "license": "MIT", + "dependencies": { + "@types/diff-match-patch": "^1.0.36", + "chalk": "^5.3.0", + "diff-match-patch": "^1.0.5" + }, + "bin": { + "jsondiffpatch": "bin/jsondiffpatch.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/jsondiffpatch/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -5193,6 +5569,69 @@ "whatwg-fetch": "^3.6.20" } }, + "node_modules/ollama-ai-provider": { + "version": "0.15.0", + "resolved": "https://registry.npmmirror.com/ollama-ai-provider/-/ollama-ai-provider-0.15.0.tgz", + "integrity": "sha512-pBRv2PjOPFdjB2fxOcu4dV3oT6NUxUI+V3c0Cu3r8Dwv6WmhHkRGLMscyRMU4Q2YVAtbghrvkfBGQmaqpMh/KQ==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "0.0.23", + "@ai-sdk/provider-utils": "1.0.18", + "partial-json": "0.1.7" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/ollama-ai-provider/node_modules/@ai-sdk/provider-utils": { + "version": "1.0.18", + "resolved": "https://registry.npmmirror.com/@ai-sdk/provider-utils/-/provider-utils-1.0.18.tgz", + "integrity": "sha512-9u/XE/dB1gsIGcxiC5JfGOLzUz+EKRXt66T8KYWwDg4x8d02P+fI/EPOgkf+T4oLBrcQgvs4GPXPKoXGPJxBbg==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "0.0.23", + "eventsource-parser": "1.1.2", + "nanoid": "3.3.6", + "secure-json-parse": "2.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/ollama-ai-provider/node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -5465,6 +5904,12 @@ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", "dev": true }, + "node_modules/partial-json": { + "version": "0.1.7", + "resolved": "https://registry.npmmirror.com/partial-json/-/partial-json-0.1.7.tgz", + "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -6248,6 +6693,12 @@ "loose-envify": "^1.1.0" } }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmmirror.com/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", + "license": "BSD-3-Clause" + }, "node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", @@ -6386,6 +6837,18 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/sswr": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/sswr/-/sswr-2.1.0.tgz", + "integrity": "sha512-Cqc355SYlTAaUt8iDPaC/4DPPXK925PePLMxyBKuWd5kKc5mwsG3nT9+Mq2tyguL5s7b4Jg+IRMpTRsNTAfpSQ==", + "license": "MIT", + "dependencies": { + "swrev": "^4.0.0" + }, + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0-next.0" + } + }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -6687,6 +7150,34 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swr": { + "version": "2.2.5", + "resolved": "https://registry.npmmirror.com/swr/-/swr-2.2.5.tgz", + "integrity": "sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==", + "license": "MIT", + "dependencies": { + "client-only": "^0.0.1", + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/swrev": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/swrev/-/swrev-4.0.0.tgz", + "integrity": "sha512-LqVcOHSB4cPGgitD1riJ1Hh4vdmITOp+BkmfmXRh4hSF/t7EnS4iD+SOTmq7w5pPm/SiPeto4ADbKS6dHUDWFA==", + "license": "MIT" + }, + "node_modules/swrv": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/swrv/-/swrv-1.0.4.tgz", + "integrity": "sha512-zjEkcP8Ywmj+xOJW3lIT65ciY/4AL4e/Or7Gj0MzU3zBJNMdJiT8geVZhINavnlHRMMCcJLHhraLTAiDOTmQ9g==", + "license": "Apache-2.0", + "peerDependencies": { + "vue": ">=3.2.26 < 4" + } + }, "node_modules/tailwindcss": { "version": "3.4.12", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.12.tgz", @@ -7180,6 +7671,15 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", + "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -7575,6 +8075,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zod-to-json-schema": { + "version": "3.23.2", + "resolved": "https://registry.npmmirror.com/zod-to-json-schema/-/zod-to-json-schema-3.23.2.tgz", + "integrity": "sha512-uSt90Gzc/tUfyNqxnjlfBs8W6WSGpNBv0rVsNxP/BVSMHMKGdthPYff4xtCHYloJGM0CFxFsb3NbC0eqPhfImw==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.23.3" + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/extensions/void/package.json b/extensions/void/package.json index ebccfe72..f0223115 100644 --- a/extensions/void/package.json +++ b/extensions/void/package.json @@ -137,7 +137,12 @@ "typescript-eslint": "^8.3.0" }, "dependencies": { + "@ai-sdk/anthropic": "^0.0.50", + "@ai-sdk/azure": "^0.0.39", + "@ai-sdk/openai": "^0.0.60", "@anthropic-ai/sdk": "^0.27.1", + "ai": "^3.3.43", + "ollama-ai-provider": "^0.15.0", "openai": "^4.57.0" } } diff --git a/extensions/void/src/common/sendLLMMessage.ts b/extensions/void/src/common/sendLLMMessage.ts index ca3c34bd..f68db66d 100644 --- a/extensions/void/src/common/sendLLMMessage.ts +++ b/extensions/void/src/common/sendLLMMessage.ts @@ -1,15 +1,27 @@ -import Anthropic from '@anthropic-ai/sdk'; -import OpenAI from 'openai'; - -// import ollama from 'ollama' +import { streamText } from 'ai' +import { createOpenAI, OpenAIProviderSettings } from '@ai-sdk/openai'; +import { anthropic, AnthropicProviderSettings } from '@ai-sdk/anthropic'; +import { AzureOpenAIProviderSettings, createAzure } from '@ai-sdk/azure'; +import { createOllama, OllamaProviderSettings } from 'ollama-ai-provider'; export type ApiConfig = { anthropic: { - apikey: string, + apikey: string, + /** @default 'claude-3-5-sonnet-20240620' */ + model?: string, + setting: AnthropicProviderSettings }, openai: { - apikey: string - }, + apikey: string + /** @default 'gpt-4o' */ + model?: string, + setting: OpenAIProviderSettings + }, + azure: { + apiKey: string, + deploymentId: string, + setting: AzureOpenAIProviderSettings + }, greptile: { apikey: string, githubPAT: string, @@ -19,14 +31,14 @@ export type ApiConfig = { branch: string // e.g. 'main' } }, - ollama: { - // TODO + ollama: { + /** @default 'llama3.1' */ + model: string + setting: OllamaProviderSettings }, whichApi: string } - - type OnText = (newText: string, fullText: string) => void export type LLMMessage = { @@ -54,95 +66,6 @@ type SendLLMMessageFnTypeExternal = (params: { abort: () => void } - - - -// Claude -const sendClaudeMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, apiConfig }) => { - - - const anthropic = new Anthropic({ apiKey: apiConfig.anthropic.apikey, dangerouslyAllowBrowser: true }); // defaults to process.env["ANTHROPIC_API_KEY"] - - const stream = anthropic.messages.stream({ - model: "claude-3-5-sonnet-20240620", - max_tokens: 1024, - messages: messages, - }); - - let did_abort = false - - // when receive text - stream.on('text', (newText, fullText) => { - if (did_abort) return - onText(newText, fullText) - }) - - // when we get the final message on this stream (or when error/fail) - stream.on('finalMessage', (claude_response) => { - if (did_abort) return - // stringify the response's content - let content = claude_response.content.map(c => { if (c.type === 'text') { return c.text } }).join('\n'); - onFinalMessage(content) - }) - - - // if abort is called, onFinalMessage is NOT called, and no later onTexts are called either - const abort = () => { - // stream.abort() // this doesnt appear to do anything, but it should try to stop claude from generating anymore - did_abort = true - } - - return { abort } - -}; - - - - -// OpenAI -const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, apiConfig }) => { - - let did_abort = false - let fullText = '' - - // if abort is called, onFinalMessage is NOT called, and no later onTexts are called either - let abort: () => void = () => { did_abort = true } - - const openai = new OpenAI({ apiKey: apiConfig.openai.apikey, dangerouslyAllowBrowser: true }); - - openai.chat.completions.create({ - model: 'gpt-4o-2024-08-06', - messages: messages, - stream: true, - }) - .then(async response => { - abort = () => { - // response.controller.abort() // this isn't needed now, to keep consistency with claude will leave it commented - did_abort = true; - } - // when receive text - try { - for await (const chunk of response) { - if (did_abort) 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); - } - // when we get the final message on this stream - onFinalMessage(fullText) - }) - return { abort }; -}; - - - // Greptile // https://docs.greptile.com/api-reference/query // https://docs.greptile.com/quickstart#sample-response-streamed @@ -207,79 +130,51 @@ const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFin .catch(e => { console.error('Error in Greptile stream:', e); onFinalMessage(fullText); - }); - return { abort } - - - } export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({ messages, onText, onFinalMessage, apiConfig }) => { - if (!apiConfig) return { abort: () => { } } + if (!apiConfig) return { abort: () => { } } + const whichApi = apiConfig.whichApi + // TODO: create an @ai-sdk provider for greptile + if (whichApi === 'greptile') + return sendGreptileMsg({ messages, onText, onFinalMessage, apiConfig }) - const whichApi = apiConfig.whichApi - - if (whichApi === 'anthropic') { - return sendClaudeMsg({ messages, onText, onFinalMessage, apiConfig }) - } - else if (whichApi === 'openai') { - return sendOpenAIMsg({ messages, onText, onFinalMessage, apiConfig }) - } - else if (whichApi === 'greptile') { - return sendGreptileMsg({ messages, onText, onFinalMessage, apiConfig }) - } - else if (whichApi === 'ollama') { - return sendClaudeMsg({ messages, onText, onFinalMessage, apiConfig }) // TODO - } - else { - console.error(`Error: whichApi was ${whichApi}, which is not recognized!`) - return sendClaudeMsg({ messages, onText, onFinalMessage, apiConfig }) // TODO - } + const model = getAiModel(apiConfig.whichApi, apiConfig) + const abortController = new AbortController() + const abortSignal = abortController.signal + streamText({ + model, + messages, + abortSignal, + }).then(async (result) => { + let fullText = '' + for await (const textPart of result.textStream) { + fullText += textPart + onText(textPart, fullText) + } + onFinalMessage(fullText) + }) + return { abort: abortController.abort } } - -// Ollama -// const sendOllamaMsg: sendMsgFnType = ({ messages, onText, onFinalMessage }) => { - -// let did_abort = false -// let fullText = '' - -// // if abort is called, onFinalMessage is NOT called, and no later onTexts are called either -// let abort: () => void = () => { -// did_abort = true -// } - -// ollama.chat({ model: 'llama3.1', messages: messages, stream: true }) -// .then(async response => { - -// abort = () => { -// // response.abort() // this isn't needed now, to keep consistency with claude will leave it commented for now -// did_abort = true; -// } - -// // when receive text -// try { -// for await (const part of response) { -// if (did_abort) return -// let newText = part.message.content -// fullText += newText -// onText(newText, fullText) -// } -// } -// // when error/fail -// catch (e) { -// onFinalMessage(fullText) -// return -// } - -// // when we get the final message on this stream -// onFinalMessage(fullText) -// }) - -// return { abort }; -// }; - +export const getAiModel = (provider: string, apiConfig?: ApiConfig | null) => { + switch (provider) { + case 'openai': return createOpenAI({ + apiKey: apiConfig?.openai.apikey, + })(apiConfig?.openai.model || 'gpt-4o') + case 'anthropic': return anthropic(apiConfig?.anthropic.model || 'claude-3-5-sonnet-20240620') + case 'ollama': return createOllama(apiConfig?.ollama.setting)(apiConfig?.ollama.model || 'llama3.1') + case 'azure': { + if (!apiConfig?.azure.deploymentId) { + throw new Error(`Error: azure deploymentId is not defined`) + } + return createAzure(apiConfig?.azure.setting)(apiConfig?.azure.deploymentId) + } + default: + throw new Error(`Error: provider was ${provider}, which is not recognized!`) + } +} From 7bcefd45360bba91252d65857f78c35fc3e110ee Mon Sep 17 00:00:00 2001 From: Darion Date: Fri, 20 Sep 2024 13:56:21 -0400 Subject: [PATCH 03/67] Update extensions/void/package.json Co-authored-by: Bruce MacDonald --- extensions/void/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/void/package.json b/extensions/void/package.json index 21be74ed..7d139bff 100644 --- a/extensions/void/package.json +++ b/extensions/void/package.json @@ -43,7 +43,7 @@ "void.ollamaSettings.endpoint": { "type": "string", "default": "", - "description": "Ollama Endpoint - Local API server can be started with `OLLAMA_ORIGINS=* ollama serve`" + "description": "Ollama Endpoint - Local API server can be started with `OLLAMA_ORIGINS="vscode-webview://*" ollama serve`" }, "void.ollamaSettings.model": { "type": "string", From 78e10a5593839e4f741a6ad286c72343e260f9ba Mon Sep 17 00:00:00 2001 From: Jelf Date: Sat, 21 Sep 2024 02:15:19 +0800 Subject: [PATCH 04/67] feat: update ollama config --- extensions/void/package-lock.json | 363 ++++++++++++++++-- extensions/void/src/SidebarWebviewProvider.ts | 4 +- extensions/void/src/common/sendLLMMessage.ts | 202 +++++----- extensions/void/src/extension.ts | 9 +- 4 files changed, 449 insertions(+), 129 deletions(-) diff --git a/extensions/void/package-lock.json b/extensions/void/package-lock.json index f2b84f0f..6ebe684b 100644 --- a/extensions/void/package-lock.json +++ b/extensions/void/package-lock.json @@ -14,7 +14,8 @@ "@anthropic-ai/sdk": "^0.27.1", "ai": "^3.3.43", "ollama-ai-provider": "^0.15.0", - "openai": "^4.57.0" + "openai": "^4.57.0", + "zod": "^3.23.8" }, "devDependencies": { "@eslint/js": "^9.9.1", @@ -296,6 +297,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@anthropic-ai/sdk": { "version": "0.27.3", "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.27.3.tgz", @@ -336,11 +350,19 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.8", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-validator-identifier": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -422,6 +444,35 @@ "node": ">=4" } }, + "node_modules/@babel/parser": { + "version": "7.25.6", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.25.6.tgz", + "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", + "peer": true, + "dependencies": { + "@babel/types": "^7.25.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.25.6", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.25.6.tgz", + "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", + "peer": true, + "dependencies": { + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", @@ -692,7 +743,6 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", - "dev": true, "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -706,7 +756,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "engines": { "node": ">=6.0.0" } @@ -715,7 +764,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, "engines": { "node": ">=6.0.0" } @@ -723,14 +771,12 @@ "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -820,8 +866,7 @@ "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" }, "node_modules/@types/estree-jsx": { "version": "1.0.5", @@ -1198,6 +1243,118 @@ "node": ">=16" } }, + "node_modules/@vue/compiler-core": { + "version": "3.5.7", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.7.tgz", + "integrity": "sha512-A0gay3lK71MddsSnGlBxRPOugIVdACze9L/rCo5X5srCyjQfZOfYtSFMJc3aOZCM+xN55EQpb4R97rYn/iEbSw==", + "peer": true, + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/shared": "3.5.7", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-core/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "peer": true + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.7", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.7.tgz", + "integrity": "sha512-GYWl3+gO8/g0ZdYaJ18fYHdI/WVic2VuuUd1NsPp60DWXKy+XjdhFsDW7FbUto8siYYZcosBGn9yVBkjhq1M8Q==", + "peer": true, + "dependencies": { + "@vue/compiler-core": "3.5.7", + "@vue/shared": "3.5.7" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.7", + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.7.tgz", + "integrity": "sha512-EjOJtCWJrC7HqoCEzOwpIYHm+JH7YmkxC1hG6VkqIukYRqj8KFUlTLK6hcT4nGgtVov2+ZfrdrRlcaqS78HnBA==", + "peer": true, + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/compiler-core": "3.5.7", + "@vue/compiler-dom": "3.5.7", + "@vue/compiler-ssr": "3.5.7", + "@vue/shared": "3.5.7", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.11", + "postcss": "^8.4.47", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "peer": true + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.7", + "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.7.tgz", + "integrity": "sha512-oZx+jXP2k5arV/8Ly3TpQbfFyimMw2ANrRqvHJoKjPqtEzazxQGZjCLOfq8TnZ3wy2TOXdqfmVp4q7FyYeHV4g==", + "peer": true, + "dependencies": { + "@vue/compiler-dom": "3.5.7", + "@vue/shared": "3.5.7" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.7", + "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.7.tgz", + "integrity": "sha512-yF0EpokpOHRNXyn/h6abXc9JFIzfdAf0MJHIi92xxCWS0mqrXH6+2aZ+A6EbSrspGzX5MHTd5N8iBA28HnXu9g==", + "peer": true, + "dependencies": { + "@vue/shared": "3.5.7" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.7", + "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.7.tgz", + "integrity": "sha512-OzLpBpKbZEaZVSNfd+hQbfBrDKux+b7Yl5hYhhWWWhHD7fEpF+CdI3Brm5k5GsufHEfvMcjruPxwQZuBN6nFYQ==", + "peer": true, + "dependencies": { + "@vue/reactivity": "3.5.7", + "@vue/shared": "3.5.7" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.7", + "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.7.tgz", + "integrity": "sha512-fL7cETfE27U2jyTgqzE382IGFY6a6uyznErn27KbbEzNctzxxUWYDbaN3B55l9nXh0xW2LRWPuWKOvjtO2UewQ==", + "peer": true, + "dependencies": { + "@vue/reactivity": "3.5.7", + "@vue/runtime-core": "3.5.7", + "@vue/shared": "3.5.7", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.7", + "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.7.tgz", + "integrity": "sha512-peRypij815eIDjpPpPXvYQGYqPH6QXwLJGWraJYPPn8JqWGl29A8QXnS7/Mh3TkMiOcdsJNhbFCoW2Agc2NgAQ==", + "peer": true, + "dependencies": { + "@vue/compiler-ssr": "3.5.7", + "@vue/shared": "3.5.7" + }, + "peerDependencies": { + "vue": "3.5.7" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.7", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.7.tgz", + "integrity": "sha512-NBE1PBIvzIedxIc2RZiKXvGbJkrZ2/hLf3h8GlS4/sP9xcXEZMFWOazFkNd6aGeUCMaproe5MHVYB3/4AW9q9g==", + "peer": true + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -1213,7 +1370,6 @@ "version": "8.12.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", - "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -1400,6 +1556,15 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/aria-query": { + "version": "5.3.1", + "resolved": "https://registry.npmmirror.com/aria-query/-/aria-query-5.3.1.tgz", + "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/array-buffer-byte-length": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", @@ -1587,6 +1752,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/bail": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", @@ -2047,6 +2221,19 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/code-red": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/code-red/-/code-red-1.0.4.tgz", + "integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==", + "peer": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15", + "@types/estree": "^1.0.1", + "acorn": "^8.10.0", + "estree-walker": "^3.0.3", + "periscopic": "^3.1.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2127,6 +2314,19 @@ "node": ">= 8" } }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "peer": true, + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -2142,8 +2342,7 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/data-view-buffer": { "version": "1.0.1", @@ -2387,6 +2586,18 @@ "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "peer": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-abstract": { "version": "1.23.3", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", @@ -2893,6 +3104,15 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "peer": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -3952,6 +4172,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-reference": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/is-reference/-/is-reference-3.0.2.tgz", + "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", + "peer": true, + "dependencies": { + "@types/estree": "*" + } + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -4277,8 +4506,7 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { "version": "4.1.0", @@ -4418,6 +4646,12 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "peer": true + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -4469,7 +4703,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -4483,6 +4716,15 @@ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true }, + "node_modules/magic-string": { + "version": "0.30.11", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "peer": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -4662,6 +4904,12 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "peer": true + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -5373,7 +5621,6 @@ "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, "funding": [ { "type": "github", @@ -5959,11 +6206,21 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/periscopic": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/periscopic/-/periscopic-3.1.0.tgz", + "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", + "peer": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^3.0.0", + "is-reference": "^3.0.0" + } + }, "node_modules/picocolors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", - "dev": true + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -6008,7 +6265,6 @@ "version": "8.4.47", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -6286,7 +6542,6 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "dev": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -6822,7 +7077,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -7150,6 +7404,31 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svelte": { + "version": "4.2.19", + "resolved": "https://registry.npmmirror.com/svelte/-/svelte-4.2.19.tgz", + "integrity": "sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==", + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.2.1", + "@jridgewell/sourcemap-codec": "^1.4.15", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/estree": "^1.0.1", + "acorn": "^8.9.0", + "aria-query": "^5.3.0", + "axobject-query": "^4.0.0", + "code-red": "^1.0.3", + "css-tree": "^2.3.1", + "estree-walker": "^3.0.3", + "is-reference": "^3.0.1", + "locate-character": "^3.0.0", + "magic-string": "^0.30.4", + "periscopic": "^3.1.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/swr": { "version": "2.2.5", "resolved": "https://registry.npmmirror.com/swr/-/swr-2.2.5.tgz", @@ -7337,6 +7616,15 @@ "node": ">=0.8" } }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "peer": true, + "engines": { + "node": ">=4" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -7493,7 +7781,7 @@ "version": "5.5.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", - "dev": true, + "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -7728,6 +8016,27 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/vue": { + "version": "3.5.7", + "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.7.tgz", + "integrity": "sha512-JcFm0f5j8DQO9E07pZRxqZ/ZsNopMVzHYXpKvnfqXFcA4JTi+4YcrikRn9wkzWsdj0YsLzlLIsR0zzGxA2P6Wg==", + "peer": true, + "dependencies": { + "@vue/compiler-dom": "3.5.7", + "@vue/compiler-sfc": "3.5.7", + "@vue/runtime-dom": "3.5.7", + "@vue/server-renderer": "3.5.7", + "@vue/shared": "3.5.7" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/web-streams-polyfill": { "version": "4.0.0-beta.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", @@ -8075,6 +8384,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmmirror.com/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/zod-to-json-schema": { "version": "3.23.2", "resolved": "https://registry.npmmirror.com/zod-to-json-schema/-/zod-to-json-schema-3.23.2.tgz", diff --git a/extensions/void/src/SidebarWebviewProvider.ts b/extensions/void/src/SidebarWebviewProvider.ts index fe01c800..7fd8c83e 100644 --- a/extensions/void/src/SidebarWebviewProvider.ts +++ b/extensions/void/src/SidebarWebviewProvider.ts @@ -55,7 +55,9 @@ export class SidebarWebviewProvider implements vscode.WebviewViewProvider { const nonce = getNonce(); // only scripts with the nonce are allowed to run, this is a recommended security measure - const allowed_urls = ['https://api.anthropic.com', 'https://api.openai.com', 'https://api.greptile.com'] + // Allow Ollama endpoint + const ollamaEndpoint = vscode.workspace.getConfiguration('void').get('ollamaSettings.endpoint') || 'http://localhost:11434' + const allowed_urls = ['https://api.anthropic.com', 'https://api.openai.com', 'https://api.greptile.com', ollamaEndpoint ] webview.html = ` diff --git a/extensions/void/src/common/sendLLMMessage.ts b/extensions/void/src/common/sendLLMMessage.ts index f68db66d..758c4f8e 100644 --- a/extensions/void/src/common/sendLLMMessage.ts +++ b/extensions/void/src/common/sendLLMMessage.ts @@ -1,70 +1,70 @@ import { streamText } from 'ai' import { createOpenAI, OpenAIProviderSettings } from '@ai-sdk/openai'; -import { anthropic, AnthropicProviderSettings } from '@ai-sdk/anthropic'; +import { AnthropicProviderSettings, createAnthropic } from '@ai-sdk/anthropic'; import { AzureOpenAIProviderSettings, createAzure } from '@ai-sdk/azure'; import { createOllama, OllamaProviderSettings } from 'ollama-ai-provider'; export type ApiConfig = { - anthropic: { - apikey: string, + anthropic: { + apiKey: string, /** @default 'claude-3-5-sonnet-20240620' */ model?: string, - setting: AnthropicProviderSettings - }, - openai: { - apikey: string + setting?: AnthropicProviderSettings + }, + openai: { + apiKey: string, /** @default 'gpt-4o' */ model?: string, - setting: OpenAIProviderSettings + setting?: OpenAIProviderSettings }, azure: { apiKey: string, deploymentId: string, - setting: AzureOpenAIProviderSettings + setting?: AzureOpenAIProviderSettings + }, + greptile: { + apikey: string, + githubPAT: string, + repoinfo: { + remote: string, // e.g. 'github' + repository: string, // e.g. 'voideditor/void' + branch: string // e.g. 'main' + } }, - greptile: { - apikey: string, - githubPAT: string, - repoinfo: { - remote: string, // e.g. 'github' - repository: string, // e.g. 'voideditor/void' - branch: string // e.g. 'main' - } - }, ollama: { /** @default 'llama3.1' */ model: string setting: OllamaProviderSettings - }, - whichApi: string + }, + whichApi: string } type OnText = (newText: string, fullText: string) => void export type LLMMessage = { - role: 'user' | 'assistant', - content: string + role: 'user' | 'assistant', + content: string } type SendLLMMessageFnTypeInternal = (params: { - messages: LLMMessage[], - onText: OnText, - onFinalMessage: (input: string) => void, - apiConfig: ApiConfig, + messages: LLMMessage[], + onText: OnText, + onFinalMessage: (input: string) => void, + apiConfig: ApiConfig, }) - => { - abort: () => void - } + => { + abort: () => void + } type SendLLMMessageFnTypeExternal = (params: { - messages: LLMMessage[], - onText: OnText, - onFinalMessage: (input: string) => void, - apiConfig: ApiConfig | null, + messages: LLMMessage[], + onText: OnText, + onFinalMessage: (input: string) => void, + apiConfig: ApiConfig | null, }) - => { - abort: () => void - } + => { + abort: () => void + } // Greptile // https://docs.greptile.com/api-reference/query @@ -72,66 +72,66 @@ type SendLLMMessageFnTypeExternal = (params: { const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, apiConfig }) => { - let did_abort = false - let fullText = '' + let did_abort = false + let fullText = '' - // if abort is called, onFinalMessage is NOT called, and no later onTexts are called either - let abort: () => void = () => { did_abort = true } + // if abort is called, onFinalMessage is NOT called, and no later onTexts are called either + let abort: () => void = () => { did_abort = true } - fetch('https://api.greptile.com/v2/query', { - method: 'POST', - headers: { - "Authorization": `Bearer ${apiConfig.greptile.apikey}`, - "X-Github-Token": `${apiConfig.greptile.githubPAT}`, - "Content-Type": `application/json`, - }, - body: JSON.stringify({ - messages, - stream: true, - repositories: [apiConfig.greptile.repoinfo] - }), - }) - // this is {message}\n{message}\n{message}...\n - .then(async response => { - const text = await response.text() - console.log('got greptile', text) - return JSON.parse(`[${text.trim().split('\n').join(',')}]`) - }) - // TODO make this actually stream, right now it just sends one message at the end - .then(async responseArr => { - if (did_abort) - return + fetch('https://api.greptile.com/v2/query', { + method: 'POST', + headers: { + "Authorization": `Bearer ${apiConfig.greptile.apikey}`, + "X-Github-Token": `${apiConfig.greptile.githubPAT}`, + "Content-Type": `application/json`, + }, + body: JSON.stringify({ + messages, + stream: true, + repositories: [apiConfig.greptile.repoinfo] + }), + }) + // this is {message}\n{message}\n{message}...\n + .then(async response => { + const text = await response.text() + console.log('got greptile', text) + return JSON.parse(`[${text.trim().split('\n').join(',')}]`) + }) + // TODO make this actually stream, right now it just sends one message at the end + .then(async responseArr => { + if (did_abort) + return - for (let response of responseArr) { + for (let response of responseArr) { - const type: string = response['type'] - const message = response['message'] + const type: string = response['type'] + const message = response['message'] - // when receive text - if (type === 'message') { - fullText += message - onText(message, fullText) - } - else if (type === 'sources') { - const { filepath, linestart, lineend } = message as { filepath: string, linestart: number | null, lineend: number | null } - fullText += filepath - onText(filepath, fullText) - } - // type: 'status' with an empty 'message' means last message - else if (type === 'status') { - if (!message) { - onFinalMessage(fullText) - } - } - } + // when receive text + if (type === 'message') { + fullText += message + onText(message, fullText) + } + else if (type === 'sources') { + const { filepath, linestart, lineend } = message as { filepath: string, linestart: number | null, lineend: number | null } + fullText += filepath + onText(filepath, fullText) + } + // type: 'status' with an empty 'message' means last message + else if (type === 'status') { + if (!message) { + onFinalMessage(fullText) + } + } + } - }) - .catch(e => { - console.error('Error in Greptile stream:', e); - onFinalMessage(fullText); - }); - return { abort } + }) + .catch(e => { + console.error('Error in Greptile stream:', e); + onFinalMessage(fullText); + }); + return { abort } } @@ -139,10 +139,10 @@ export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({ messages, onText, if (!apiConfig) return { abort: () => { } } const whichApi = apiConfig.whichApi // TODO: create an @ai-sdk provider for greptile - if (whichApi === 'greptile') + if (whichApi === 'greptile') return sendGreptileMsg({ messages, onText, onFinalMessage, apiConfig }) - const model = getAiModel(apiConfig.whichApi, apiConfig) + const model = getAiModel(apiConfig) const abortController = new AbortController() const abortSignal = abortController.signal streamText({ @@ -161,20 +161,18 @@ export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({ messages, onText, return { abort: abortController.abort } } -export const getAiModel = (provider: string, apiConfig?: ApiConfig | null) => { - switch (provider) { - case 'openai': return createOpenAI({ - apiKey: apiConfig?.openai.apikey, - })(apiConfig?.openai.model || 'gpt-4o') - case 'anthropic': return anthropic(apiConfig?.anthropic.model || 'claude-3-5-sonnet-20240620') - case 'ollama': return createOllama(apiConfig?.ollama.setting)(apiConfig?.ollama.model || 'llama3.1') - case 'azure': { - if (!apiConfig?.azure.deploymentId) { +export const getAiModel = (apiConfig: ApiConfig) => { + switch (apiConfig.whichApi) { + case 'openai': return createOpenAI(apiConfig.openai.setting)(apiConfig.openai.model || 'gpt-4o') + case 'anthropic': return createAnthropic(apiConfig.anthropic.setting)(apiConfig.anthropic.model || 'claude-3-5-sonnet-20240620') + case 'ollama': return createOllama(apiConfig.ollama.setting)(apiConfig.ollama.model || 'llama3.1') + case 'azure': { + if (!apiConfig.azure.deploymentId) { throw new Error(`Error: azure deploymentId is not defined`) } - return createAzure(apiConfig?.azure.setting)(apiConfig?.azure.deploymentId) + return createAzure(apiConfig.azure.setting)(apiConfig.azure.deploymentId) } default: - throw new Error(`Error: provider was ${provider}, which is not recognized!`) + throw new Error(`Error: provider was ${apiConfig.whichApi}, which is not recognized!`) } } diff --git a/extensions/void/src/extension.ts b/extensions/void/src/extension.ts index 4cd8ac26..03016d0f 100644 --- a/extensions/void/src/extension.ts +++ b/extensions/void/src/extension.ts @@ -13,8 +13,8 @@ const readFileContentOfUri = async (uri: vscode.Uri) => { const getApiConfig = () => { const apiConfig: ApiConfig = { - anthropic: { apikey: vscode.workspace.getConfiguration('void').get('anthropicApiKey') ?? '' }, - openai: { apikey: vscode.workspace.getConfiguration('void').get('openAIApiKey') ?? '' }, + anthropic: { apiKey: vscode.workspace.getConfiguration('void').get('anthropicApiKey') ?? '' }, + openai: { apiKey: vscode.workspace.getConfiguration('void').get('openAIApiKey') ?? '' }, greptile: { apikey: vscode.workspace.getConfiguration('void').get('greptileApiKey') ?? '', githubPAT: vscode.workspace.getConfiguration('void').get('githubPAT') ?? '', @@ -25,7 +25,10 @@ const getApiConfig = () => { } }, ollama: { - // apikey: vscode.workspace.getConfiguration('void').get('ollamaSettings') ?? '', + model: vscode.workspace.getConfiguration('void').get('ollamaSettings.model') ?? '', + setting: { + baseURL: vscode.workspace.getConfiguration('void').get('ollamaSettings.baseURL') ?? '', + }, }, whichApi: vscode.workspace.getConfiguration('void').get('whichApi') ?? '' } From 2b3f82865427802b25c738b2f587ae90487eed1e Mon Sep 17 00:00:00 2001 From: w1gs Date: Sat, 21 Sep 2024 00:24:51 -0400 Subject: [PATCH 05/67] Fixed parsing error in package.json --- extensions/void/package-lock.json | 16 ---------------- extensions/void/package.json | 2 +- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/extensions/void/package-lock.json b/extensions/void/package-lock.json index cb1b8f87..1c49e24a 100644 --- a/extensions/void/package-lock.json +++ b/extensions/void/package-lock.json @@ -32,7 +32,6 @@ "eslint-plugin-react-hooks": "^4.6.2", "globals": "^15.9.0", "marked": "^14.1.0", - "ollama": "^0.5.8", "postcss": "^8.4.41", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -5184,15 +5183,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ollama": { - "version": "0.5.9", - "resolved": "https://registry.npmjs.org/ollama/-/ollama-0.5.9.tgz", - "integrity": "sha512-F/KZuDRC+ZsVCuMvcOYuQ6zj42/idzCkkuknGyyGVmNStMZ/sU3jQpvhnl4SyC0+zBzLiKNZJnJeuPFuieWZvQ==", - "dev": true, - "dependencies": { - "whatwg-fetch": "^3.6.20" - } - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -7241,12 +7231,6 @@ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, - "node_modules/whatwg-fetch": { - "version": "3.6.20", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", - "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", - "dev": true - }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", diff --git a/extensions/void/package.json b/extensions/void/package.json index 7d139bff..a0267896 100644 --- a/extensions/void/package.json +++ b/extensions/void/package.json @@ -43,7 +43,7 @@ "void.ollamaSettings.endpoint": { "type": "string", "default": "", - "description": "Ollama Endpoint - Local API server can be started with `OLLAMA_ORIGINS="vscode-webview://*" ollama serve`" + "description": "Ollama Endpoint - Local API server can be started with `OLLAMA_ORIGINS=\"vscode-webview://*\" ollama serve`" }, "void.ollamaSettings.model": { "type": "string", From e8cf6ee9e7260a9c545d572d5d8fac0c81ff28ad Mon Sep 17 00:00:00 2001 From: w1gs Date: Sat, 21 Sep 2024 00:36:11 -0400 Subject: [PATCH 06/67] Replaced fetch with Ollama client --- extensions/void/package-lock.json | 14 +++++ extensions/void/package.json | 1 + extensions/void/src/common/sendLLMMessage.ts | 66 ++++++-------------- 3 files changed, 33 insertions(+), 48 deletions(-) diff --git a/extensions/void/package-lock.json b/extensions/void/package-lock.json index 1c49e24a..354cc7e7 100644 --- a/extensions/void/package-lock.json +++ b/extensions/void/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.1", "dependencies": { "@anthropic-ai/sdk": "^0.27.1", + "ollama": "^0.5.9", "openai": "^4.57.0" }, "devDependencies": { @@ -5183,6 +5184,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ollama": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/ollama/-/ollama-0.5.9.tgz", + "integrity": "sha512-F/KZuDRC+ZsVCuMvcOYuQ6zj42/idzCkkuknGyyGVmNStMZ/sU3jQpvhnl4SyC0+zBzLiKNZJnJeuPFuieWZvQ==", + "dependencies": { + "whatwg-fetch": "^3.6.20" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -7231,6 +7240,11 @@ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==" + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", diff --git a/extensions/void/package.json b/extensions/void/package.json index a0267896..4e66383d 100644 --- a/extensions/void/package.json +++ b/extensions/void/package.json @@ -142,6 +142,7 @@ }, "dependencies": { "@anthropic-ai/sdk": "^0.27.1", + "ollama": "^0.5.9", "openai": "^4.57.0" } } diff --git a/extensions/void/src/common/sendLLMMessage.ts b/extensions/void/src/common/sendLLMMessage.ts index bf87f40b..2fb3dd3d 100644 --- a/extensions/void/src/common/sendLLMMessage.ts +++ b/extensions/void/src/common/sendLLMMessage.ts @@ -1,5 +1,6 @@ import Anthropic from '@anthropic-ai/sdk'; import OpenAI from 'openai'; +import { Ollama } from 'ollama/browser' export type ApiConfig = { anthropic: { @@ -239,6 +240,8 @@ export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({ messages, onText, // Ollama export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, apiConfig }) => { + const ollamaClient = new Ollama({ host: apiConfig.ollama.endpoint }) + let didAbort = false; let fullText = ""; @@ -247,57 +250,24 @@ export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, didAbort = true; }; - const handleError = (error: any) => { - console.error('Error:', error); - onFinalMessage(fullText); - }; - - if (apiConfig.ollama.endpoint.endsWith('/')) { - apiConfig.ollama.endpoint = apiConfig.ollama.endpoint.slice(0, -1); - } - - fetch(`${apiConfig.ollama.endpoint}/api/chat`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - model: apiConfig.ollama.model, - messages: messages, - stream: true, - }), + ollamaClient.chat({ + model: apiConfig.ollama.model, + messages: messages, + stream: true, }) - .then(response => { - if (didAbort) return; - const reader = response.body?.getReader(); - if (!reader) { - onFinalMessage(fullText); - return; + .then(async (stream) => { + for await (const chunk of stream) { + if (didAbort) return; + const newText = chunk.message.content; + fullText += newText; + onText(newText, fullText); } - return reader; + onFinalMessage(fullText); }) - .then(reader => { - if (!reader) return; - - const readStream = async () => { - try { - let done, value; - while ({ done, value } = await reader.read(), !done) { - if (didAbort) return; - const stringedResponse = new TextDecoder().decode(value); - const newText = JSON.parse(stringedResponse).message.content; - fullText += newText; - onText(newText, fullText); - } - onFinalMessage(fullText); - } catch (error) { - handleError(error); - } - }; - - readStream(); - }) - .catch(handleError); + .catch((error) => { + console.error('Error:', error); + onFinalMessage(fullText); + }); return { abort }; }; From 7b31f2841a90a89c98e79ff5d509bdaec3da4909 Mon Sep 17 00:00:00 2001 From: Jelf Date: Sat, 21 Sep 2024 14:16:27 +0800 Subject: [PATCH 07/67] chore: improve settings (#1) * refactor: refine `anthropic` and `openai` settings * refactor: improve configuration * chore: move to headers config * fix: ollama baseURL, add description * chore: change azure deploymentId * chore: eslint * chore: update allowed_urls --- extensions/void/package.json | 308 ++++++++++++++++-- extensions/void/src/SidebarWebviewProvider.ts | 18 +- extensions/void/src/common/sendLLMMessage.ts | 266 ++++++++------- extensions/void/src/extension.ts | 232 ++++++------- 4 files changed, 553 insertions(+), 271 deletions(-) diff --git a/extensions/void/package.json b/extensions/void/package.json index f0223115..4d6d54b0 100644 --- a/extensions/void/package.json +++ b/extensions/void/package.json @@ -15,35 +15,297 @@ "configuration": { "title": "API Keys", "properties": { - "void.whichApi": { + "void.provider": { "type": "string", "default": "anthropic", - "description": "Choose a model to use (anthropic | openai | greptile | ollama)" + "description": "Choose a provider (openai | anthropic | azure | greptile | ollama)", + "enum": [ + "openai", + "anthropic", + "azure", + "greptile", + "ollama" + ] }, - "void.anthropicApiKey": { - "type": "string", - "default": "", - "description": "Anthropic API Key" + "void.anthropic": { + "type": "object", + "properties": { + "model": { + "type": "string", + "default": "claude-3-5-sonnet-20240620", + "description": "Choose a model ('claude-3-5-sonnet-20240620' | 'claude-3-opus-20240229' | 'claude-3-sonnet-20240229' | 'claude-3-haiku-20240307')", + "enum": [ + "claude-3-5-sonnet-20240620", + "claude-3-opus-20240229", + "claude-3-sonnet-20240229", + "claude-3-haiku-20240307" + ] + }, + "providerSettings": { + "type": "object", + "properties": { + "baseURL": { + "type": "string", + "description": "Use a different URL prefix for API calls, e.g. to use proxy servers." + }, + "apiKey": { + "type": "string", + "description": "Anthropic API key." + }, + "headers": { + "type": "object", + "description": "Custom headers to include in the requests." + } + } + } + } }, - "void.openAIApiKey": { - "type": "string", - "default": "", - "description": "OpenAI API Key" + "void.openai": { + "type": "object", + "properties": { + "model": { + "type": "string", + "default": "gpt-4o", + "description": "Choose a model ('o1-preview' | 'o1-mini' | 'gpt-4o' | 'gpt-4o-2024-05-13' | 'gpt-4o-2024-08-06' | 'gpt-4o-mini' | 'gpt-4o-mini-2024-07-18' | 'gpt-4-turbo' | 'gpt-4-turbo-2024-04-09' | 'gpt-4-turbo-preview' | 'gpt-4-0125-preview' | 'gpt-4-1106-preview' | 'gpt-4' | 'gpt-4-0613' | 'gpt-3.5-turbo-0125' | 'gpt-3.5-turbo' | 'gpt-3.5-turbo-1106')", + "enum": [ + "o1-preview", + "o1-mini", + "gpt-4o", + "gpt-4o-2024-05-13", + "gpt-4o-2024-08-06", + "gpt-4o-mini", + "gpt-4o-mini-2024-07-18", + "gpt-4-turbo", + "gpt-4-turbo-2024-04-09", + "gpt-4-turbo-preview", + "gpt-4-0125-preview", + "gpt-4-1106-preview", + "gpt-4", + "gpt-4-0613", + "gpt-3.5-turbo-0125", + "gpt-3.5-turbo", + "gpt-3.5-turbo-1106" + ] + }, + "providerSettings": { + "type": "object", + "properties": { + "baseURL": { + "type": "string", + "description": "Use a different URL prefix for API calls, e.g. to use proxy servers." + }, + "apiKey": { + "type": "string", + "description": "OpenAI API key." + }, + "headers": { + "type": "object", + "description": "Custom headers to include in the requests." + }, + "organization": { + "type": "string", + "description": "OpenAI Organization." + }, + "project": { + "type": "string", + "description": "OpenAI project." + }, + "compatibility": { + "type": "string", + "description": "OpenAI compatibility mode. Should be set to `strict` when using the OpenAI API, and `compatible` when using 3rd party providers. In `compatible` mode, newer information such as streamOptions are not being sent. Defaults to 'compatible'.", + "enum": [ + "strict", + "compatible" + ] + } + } + } + } }, - "void.greptileApiKey": { - "type": "string", - "default": "", - "description": "Greptile API Key" + "void.azure": { + "type": "object", + "properties": { + "deploymentId": { + "type": "string", + "description": "Azure API deployment ID." + }, + "providerSettings": { + "type": "object", + "properties": { + "apiKey": { + "type": "string", + "description": "Azure API key." + }, + "baseURL": { + "type": "string", + "description": "Azure API base URL." + }, + "resourceName": { + "type": "string", + "description": "Name of the Azure OpenAI resource. Either this or `baseURL` can be used. \nThe resource name is used in the assembled URL: `https://{resourceName}.openai.azure.com/openai/deployments/{modelId}{path}`" + }, + "headers": { + "type": "object", + "description": "Custom headers to include in the requests." + } + } + } + } }, - "void.githubPAT": { - "type": "string", - "default": "", - "description": "Greptile - Github PAT (gives Greptile access to your repo)" + "void.ollama": { + "type": "object", + "properties": { + "model": { + "type": "string", + "description": "Ollama model. 'codegemma' | 'codegemma:2b' | 'codegemma:7b' | 'codellama' | 'codellama:7b' | 'codellama:13b' | 'codellama:34b' | 'codellama:70b' | 'codellama:code' | 'codellama:python' | 'command-r' | 'command-r:35b' | 'command-r-plus' | 'command-r-plus:104b' | 'deepseek-coder-v2' | 'deepseek-coder-v2:16b' | 'deepseek-coder-v2:236b' | 'falcon2' | 'falcon2:11b' | 'firefunction-v2' | 'firefunction-v2:70b' | 'gemma' | 'gemma:2b' | 'gemma:7b' | 'gemma2' | 'gemma2:2b' | 'gemma2:9b' | 'gemma2:27b' | 'llama2' | 'llama2:7b' | 'llama2:13b' | 'llama2:70b' | 'llama3' | 'llama3:8b' | 'llama3:70b' | 'llama3-chatqa' | 'llama3-chatqa:8b' | 'llama3-chatqa:70b' | 'llama3-gradient' | 'llama3-gradient:8b' | 'llama3-gradient:70b' | 'llama3.1' | 'llama3.1:8b' | 'llama3.1:70b' | 'llama3.1:405b' | 'llava' | 'llava:7b' | 'llava:13b' | 'llava:34b' | 'llava-llama3' | 'llava-llama3:8b' | 'llava-phi3' | 'llava-phi3:3.8b' | 'mistral' | 'mistral:7b' | 'mistral-large' | 'mistral-large:123b' | 'mistral-nemo' | 'mistral-nemo:12b' | 'mixtral' | 'mixtral:8x7b' | 'mixtral:8x22b' | 'moondream' | 'moondream:1.8b' | 'openhermes' | 'openhermes:v2.5' | 'phi3' | 'phi3:3.8b' | 'phi3:14b' | 'phi3.5' | 'phi3.5:3.8b' | 'qwen' | 'qwen:7b' | 'qwen:14b' | 'qwen:32b' | 'qwen:72b' | 'qwen:110b' | 'qwen2' | 'qwen2:0.5b' | 'qwen2:1.5b' | 'qwen2:7b' | 'qwen2:72b' | 'smollm' | 'smollm:135m' | 'smollm:360m' | 'smollm:1.7b'", + "default": "llama3.1", + "enum": [ + "codegemma", + "codegemma:2b", + "codegemma:7b", + "codellama", + "codellama:7b", + "codellama:13b", + "codellama:34b", + "codellama:70b", + "codellama:code", + "codellama:python", + "command-r", + "command-r:35b", + "command-r-plus", + "command-r-plus:104b", + "deepseek-coder-v2", + "deepseek-coder-v2:16b", + "deepseek-coder-v2:236b", + "falcon2", + "falcon2:11b", + "firefunction-v2", + "firefunction-v2:70b", + "gemma", + "gemma:2b", + "gemma:7b", + "gemma2", + "gemma2:2b", + "gemma2:9b", + "gemma2:27b", + "llama2", + "llama2:7b", + "llama2:13b", + "llama2:70b", + "llama3", + "llama3:8b", + "llama3:70b", + "llama3-chatqa", + "llama3-chatqa:8b", + "llama3-chatqa:70b", + "llama3-gradient", + "llama3-gradient:8b", + "llama3-gradient:70b", + "llama3.1", + "llama3.1:8b", + "llama3.1:70b", + "llama3.1:405b", + "llava", + "llava:7b", + "llava:13b", + "llava:34b", + "llava-llama3", + "llava-llama3:8b", + "llava-phi3", + "llava-phi3:3.8b", + "mistral", + "mistral:7b", + "mistral-large", + "mistral-large:123b", + "mistral-nemo", + "mistral-nemo:12b", + "mixtral", + "mixtral:8x7b", + "mixtral:8x22b", + "moondream", + "moondream:1.8b", + "openhermes", + "openhermes:v2.5", + "phi3", + "phi3:3.8b", + "phi3:14b", + "phi3.5", + "phi3.5:3.8b", + "qwen", + "qwen:7b", + "qwen:14b", + "qwen:32b", + "qwen:72b", + "qwen:110b", + "qwen2", + "qwen2:0.5b", + "qwen2:1.5b", + "qwen2:7b", + "qwen2:72b", + "smollm", + "smollm:135m", + "smollm:360m", + "smollm:1.7b" + ] + }, + "providerSettings": { + "type": "object", + "properties": { + "baseURL": { + "type": "string", + "description": "Ollama base URL. Local API server can be started with `OLLAMA_ORIGINS=* ollama serve`" + }, + "headers": { + "type": "object", + "description": "Custom headers to include in the requests." + } + } + } + } }, - "void.ollamaSettings": { - "type": "string", - "default": "", - "description": "Ollama settings (coming soon...)" + "void.greptile": { + "type": "object", + "properties": { + "providerSettings": { + "type": "object", + "properties": { + "apikey": { + "type": "string", + "description": "Greptile API key." + }, + "headers": { + "type": "object", + "description": "Custom headers to include in the requests.", + "properties": { + "X-Github-Token": { + "type": "string", + "description": "GitHub PAT." + } + } + }, + "repoinfo": { + "type": "array", + "items": { + "type": "object", + "properties": { + "remote": { + "type": "string", + "description": "GitHub remote." + }, + "repository": { + "type": "string", + "description": "GitHub repository." + }, + "branch": { + "type": "string", + "description": "GitHub branch." + } + } + } + } + } + } + } } } }, @@ -145,4 +407,4 @@ "ollama-ai-provider": "^0.15.0", "openai": "^4.57.0" } -} +} \ No newline at end of file diff --git a/extensions/void/src/SidebarWebviewProvider.ts b/extensions/void/src/SidebarWebviewProvider.ts index 7fd8c83e..c95e1e12 100644 --- a/extensions/void/src/SidebarWebviewProvider.ts +++ b/extensions/void/src/SidebarWebviewProvider.ts @@ -1,5 +1,6 @@ // renders the code from `src/sidebar` +import { AzureOpenAIProviderSettings } from '@ai-sdk/azure'; import * as vscode from 'vscode'; function getNonce() { @@ -55,9 +56,20 @@ export class SidebarWebviewProvider implements vscode.WebviewViewProvider { const nonce = getNonce(); // only scripts with the nonce are allowed to run, this is a recommended security measure - // Allow Ollama endpoint - const ollamaEndpoint = vscode.workspace.getConfiguration('void').get('ollamaSettings.endpoint') || 'http://localhost:11434' - const allowed_urls = ['https://api.anthropic.com', 'https://api.openai.com', 'https://api.greptile.com', ollamaEndpoint ] + const ollamaBaseURL = new URL('/', vscode.workspace.getConfiguration('void').get('ollama.providerSettings.baseURL') || 'http://127.0.0.1:11434').toString() + const azureProviderSettings = vscode.workspace.getConfiguration('void').get('azure.providerSettings') as AzureOpenAIProviderSettings + const azureBaseURL = new URL('/', vscode.workspace.getConfiguration('void').get('azure.providerSettings.baseURL') || `https://${azureProviderSettings.resourceName}.openai.azure.com/openai/deployments`).toString() + const openaiBaseURL = new URL('/', vscode.workspace.getConfiguration('void').get('openai.providerSettings.baseURL') || 'https://api.openai.com').toString() + const greptileBaseURL = new URL('/', vscode.workspace.getConfiguration('void').get('greptile.providerSettings.baseURL') || 'https://api.mistral.ai').toString() + const anthropicBaseURL = new URL('/', vscode.workspace.getConfiguration('void').get('anthropic.providerSettings.baseURL') || 'https://api.mistral.ai').toString() + const allowed_urls = [ + ollamaBaseURL, + azureBaseURL, + openaiBaseURL, + greptileBaseURL, + anthropicBaseURL + ] + webview.html = ` diff --git a/extensions/void/src/common/sendLLMMessage.ts b/extensions/void/src/common/sendLLMMessage.ts index 758c4f8e..256f2ca2 100644 --- a/extensions/void/src/common/sendLLMMessage.ts +++ b/extensions/void/src/common/sendLLMMessage.ts @@ -5,66 +5,67 @@ import { AzureOpenAIProviderSettings, createAzure } from '@ai-sdk/azure'; import { createOllama, OllamaProviderSettings } from 'ollama-ai-provider'; export type ApiConfig = { - anthropic: { - apiKey: string, - /** @default 'claude-3-5-sonnet-20240620' */ - model?: string, - setting?: AnthropicProviderSettings - }, - openai: { - apiKey: string, - /** @default 'gpt-4o' */ - model?: string, - setting?: OpenAIProviderSettings - }, - azure: { - apiKey: string, - deploymentId: string, - setting?: AzureOpenAIProviderSettings - }, - greptile: { - apikey: string, - githubPAT: string, - repoinfo: { - remote: string, // e.g. 'github' - repository: string, // e.g. 'voideditor/void' - branch: string // e.g. 'main' - } - }, - ollama: { - /** @default 'llama3.1' */ - model: string - setting: OllamaProviderSettings - }, - whichApi: string + /** @default 'anthropic' */ + provider: 'anthropic' | 'openai' | 'azure' | 'greptile' | 'ollama' + anthropic: { + /** @default 'claude-3-5-sonnet-20240620' */ + model: string, + providerSettings?: AnthropicProviderSettings + }, + openai: { + /** @default 'gpt-4o' */ + model: string, + providerSettings?: OpenAIProviderSettings + }, + azure: { + deploymentId: string, + providerSettings?: AzureOpenAIProviderSettings + }, + greptile: { + providerSettings?: { + apikey?: string, + githubPAT?: string, + headers?: Record, + repoinfo?: { + remote?: string, // e.g. 'github' + repository?: string, // e.g. 'voideditor/void' + branch?: string // e.g. 'main' + }[] + } + }, + ollama: { + /** @default 'llama3.1' */ + model: string + providerSettings?: OllamaProviderSettings + }, } type OnText = (newText: string, fullText: string) => void export type LLMMessage = { - role: 'user' | 'assistant', - content: string + role: 'user' | 'assistant', + content: string } type SendLLMMessageFnTypeInternal = (params: { - messages: LLMMessage[], - onText: OnText, - onFinalMessage: (input: string) => void, - apiConfig: ApiConfig, + messages: LLMMessage[], + onText: OnText, + onFinalMessage: (input: string) => void, + apiConfig: ApiConfig, }) - => { - abort: () => void - } + => { + abort: () => void + } type SendLLMMessageFnTypeExternal = (params: { - messages: LLMMessage[], - onText: OnText, - onFinalMessage: (input: string) => void, - apiConfig: ApiConfig | null, + messages: LLMMessage[], + onText: OnText, + onFinalMessage: (input: string) => void, + apiConfig: ApiConfig | null, }) - => { - abort: () => void - } + => { + abort: () => void + } // Greptile // https://docs.greptile.com/api-reference/query @@ -72,107 +73,102 @@ type SendLLMMessageFnTypeExternal = (params: { const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, apiConfig }) => { - let did_abort = false - let fullText = '' + let did_abort = false + let fullText = '' - // if abort is called, onFinalMessage is NOT called, and no later onTexts are called either - let abort: () => void = () => { did_abort = true } + // if abort is called, onFinalMessage is NOT called, and no later onTexts are called either + let abort: () => void = () => { did_abort = true } - fetch('https://api.greptile.com/v2/query', { - method: 'POST', - headers: { - "Authorization": `Bearer ${apiConfig.greptile.apikey}`, - "X-Github-Token": `${apiConfig.greptile.githubPAT}`, - "Content-Type": `application/json`, - }, - body: JSON.stringify({ - messages, - stream: true, - repositories: [apiConfig.greptile.repoinfo] - }), - }) - // this is {message}\n{message}\n{message}...\n - .then(async response => { - const text = await response.text() - console.log('got greptile', text) - return JSON.parse(`[${text.trim().split('\n').join(',')}]`) - }) - // TODO make this actually stream, right now it just sends one message at the end - .then(async responseArr => { - if (did_abort) - return + fetch('https://api.greptile.com/v2/query', { + method: 'POST', + headers: { + "Authorization": `Bearer ${apiConfig.greptile.providerSettings?.apikey}`, + "Content-Type": `application/json`, + ...apiConfig.greptile.providerSettings?.headers + }, + body: JSON.stringify({ + messages, + stream: true, + repositories: apiConfig.greptile.providerSettings?.repoinfo + }), + }) + // this is {message}\n{message}\n{message}...\n + .then(async response => { + const text = await response.text() + console.log('got greptile', text) + return JSON.parse(`[${text.trim().split('\n').join(',')}]`) + }) + // TODO make this actually stream, right now it just sends one message at the end + .then(async responseArr => { + if (did_abort) + return - for (let response of responseArr) { + for (let response of responseArr) { - const type: string = response['type'] - const message = response['message'] + const type: string = response['type'] + const message = response['message'] - // when receive text - if (type === 'message') { - fullText += message - onText(message, fullText) - } - else if (type === 'sources') { - const { filepath, linestart, lineend } = message as { filepath: string, linestart: number | null, lineend: number | null } - fullText += filepath - onText(filepath, fullText) - } - // type: 'status' with an empty 'message' means last message - else if (type === 'status') { - if (!message) { - onFinalMessage(fullText) - } - } - } + // when receive text + if (type === 'message') { + fullText += message + onText(message, fullText) + } + else if (type === 'sources') { + const { filepath, linestart, lineend } = message as { filepath: string, linestart: number | null, lineend: number | null } + fullText += filepath + onText(filepath, fullText) + } + // type: 'status' with an empty 'message' means last message + else if (type === 'status') { + if (!message) { + onFinalMessage(fullText) + } + } + } - }) - .catch(e => { - console.error('Error in Greptile stream:', e); - onFinalMessage(fullText); - }); - return { abort } + }) + .catch(e => { + console.error('Error in Greptile stream:', e); + onFinalMessage(fullText); + }); + return { abort } } export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({ messages, onText, onFinalMessage, apiConfig }) => { - if (!apiConfig) return { abort: () => { } } - const whichApi = apiConfig.whichApi - // TODO: create an @ai-sdk provider for greptile - if (whichApi === 'greptile') - return sendGreptileMsg({ messages, onText, onFinalMessage, apiConfig }) + if (!apiConfig) return { abort: () => { } } + const provider = apiConfig.provider + // TODO: create an @ai-sdk provider for greptile + if (provider === 'greptile') + return sendGreptileMsg({ messages, onText, onFinalMessage, apiConfig }) - const model = getAiModel(apiConfig) - const abortController = new AbortController() - const abortSignal = abortController.signal - streamText({ - model, - messages, - abortSignal, - }).then(async (result) => { - let fullText = '' - for await (const textPart of result.textStream) { - fullText += textPart - onText(textPart, fullText) - } - onFinalMessage(fullText) - }) + const model = getAiModel(apiConfig) + const abortController = new AbortController() + const abortSignal = abortController.signal + streamText({ + model, + messages, + abortSignal, + }).then(async (result) => { + let fullText = '' + for await (const textPart of result.textStream) { + fullText += textPart + onText(textPart, fullText) + } + onFinalMessage(fullText) + }) - return { abort: abortController.abort } + return { abort: abortController.abort } } export const getAiModel = (apiConfig: ApiConfig) => { - switch (apiConfig.whichApi) { - case 'openai': return createOpenAI(apiConfig.openai.setting)(apiConfig.openai.model || 'gpt-4o') - case 'anthropic': return createAnthropic(apiConfig.anthropic.setting)(apiConfig.anthropic.model || 'claude-3-5-sonnet-20240620') - case 'ollama': return createOllama(apiConfig.ollama.setting)(apiConfig.ollama.model || 'llama3.1') - case 'azure': { - if (!apiConfig.azure.deploymentId) { - throw new Error(`Error: azure deploymentId is not defined`) - } - return createAzure(apiConfig.azure.setting)(apiConfig.azure.deploymentId) - } - default: - throw new Error(`Error: provider was ${apiConfig.whichApi}, which is not recognized!`) - } + switch (apiConfig.provider) { + case 'openai': return createOpenAI(apiConfig.openai.providerSettings)(apiConfig.openai.model || 'gpt-4o') + case 'anthropic': return createAnthropic(apiConfig.anthropic.providerSettings)(apiConfig.anthropic.model || 'claude-3-5-sonnet-20240620') + case 'ollama': return createOllama(apiConfig.ollama.providerSettings)(apiConfig.ollama.model || 'llama3.1') + case 'azure': return createAzure(apiConfig.azure.providerSettings)(`${apiConfig.azure.deploymentId}`) + default: + throw new Error(`Error: provider was ${apiConfig.provider}, which is not recognized!`) + } } diff --git a/extensions/void/src/extension.ts b/extensions/void/src/extension.ts index 03016d0f..a0bdc9b5 100644 --- a/extensions/void/src/extension.ts +++ b/extensions/void/src/extension.ts @@ -1,156 +1,168 @@ import * as vscode from 'vscode'; import { WebviewMessage } from './shared_types'; -import { CtrlKCodeLensProvider } from './CtrlKCodeLensProvider'; import { getDiffedLines } from './getDiffedLines'; -import { ApprovalCodeLensProvider, SuggestedEdit } from './ApprovalCodeLensProvider'; +import { ApprovalCodeLensProvider } from './ApprovalCodeLensProvider'; import { SidebarWebviewProvider } from './SidebarWebviewProvider'; import { ApiConfig } from './common/sendLLMMessage'; const readFileContentOfUri = async (uri: vscode.Uri) => { - return Buffer.from(await vscode.workspace.fs.readFile(uri)).toString('utf8').replace(/\r\n/g, '\n'); // must remove windows \r or every line will appear different because of it + return Buffer.from(await vscode.workspace.fs.readFile(uri)).toString('utf8').replace(/\r\n/g, '\n'); // must remove windows \r or every line will appear different because of it } const getApiConfig = () => { - const apiConfig: ApiConfig = { - anthropic: { apiKey: vscode.workspace.getConfiguration('void').get('anthropicApiKey') ?? '' }, - openai: { apiKey: vscode.workspace.getConfiguration('void').get('openAIApiKey') ?? '' }, - greptile: { - apikey: vscode.workspace.getConfiguration('void').get('greptileApiKey') ?? '', - githubPAT: vscode.workspace.getConfiguration('void').get('githubPAT') ?? '', - repoinfo: { - remote: 'github', - repository: 'TODO', - branch: 'main' - } - }, - ollama: { - model: vscode.workspace.getConfiguration('void').get('ollamaSettings.model') ?? '', - setting: { - baseURL: vscode.workspace.getConfiguration('void').get('ollamaSettings.baseURL') ?? '', + const apiConfig: ApiConfig = { + provider: vscode.workspace.getConfiguration('void').get('provider') ?? 'anthropic', + anthropic: { + model: vscode.workspace.getConfiguration('void').get('anthropic.model') ?? 'claude-3-5-sonnet-20240620', + providerSettings: { + ...vscode.workspace.getConfiguration('void').get('anthropic.providerSettings') ?? {}, }, - }, - whichApi: vscode.workspace.getConfiguration('void').get('whichApi') ?? '' - } - return apiConfig + }, + openai: { + model: vscode.workspace.getConfiguration('void').get('openai.model') ?? 'gpt-4o', + providerSettings: { + ...vscode.workspace.getConfiguration('void').get('openai.providerSettings') ?? {}, + }, + }, + azure: { + deploymentId: vscode.workspace.getConfiguration('void').get('azure.deploymentId') ?? '', + providerSettings: { + ...vscode.workspace.getConfiguration('void').get('azure.providerSettings') ?? {}, + }, + }, + ollama: { + model: vscode.workspace.getConfiguration('void').get('ollama.model') ?? 'llama3.1', + providerSettings: { + ...vscode.workspace.getConfiguration('void').get('ollama.providerSettings') ?? {}, + }, + }, + greptile: { + providerSettings: { + ...vscode.workspace.getConfiguration('void').get('greptile.providerSettings') ?? {}, + } + }, + } + console.log(apiConfig); + return apiConfig } export function activate(context: vscode.ExtensionContext) { - // 1. Mount the chat sidebar - const webviewProvider = new SidebarWebviewProvider(context); - context.subscriptions.push( - vscode.window.registerWebviewViewProvider(SidebarWebviewProvider.viewId, webviewProvider, { webviewOptions: { retainContextWhenHidden: true } }) - ); + // 1. Mount the chat sidebar + const webviewProvider = new SidebarWebviewProvider(context); + context.subscriptions.push( + vscode.window.registerWebviewViewProvider(SidebarWebviewProvider.viewId, webviewProvider, { webviewOptions: { retainContextWhenHidden: true } }) + ); - // 2. Activate the sidebar on ctrl+l - context.subscriptions.push( - vscode.commands.registerCommand('void.ctrl+l', () => { + // 2. Activate the sidebar on ctrl+l + context.subscriptions.push( + vscode.commands.registerCommand('void.ctrl+l', () => { - const editor = vscode.window.activeTextEditor - if (!editor) - return + const editor = vscode.window.activeTextEditor + if (!editor) + return - // show the sidebar - vscode.commands.executeCommand('workbench.view.extension.voidViewContainer'); - // vscode.commands.executeCommand('vscode.moveViewToPanel', CustomViewProvider.viewId); // move to aux bar + // show the sidebar + vscode.commands.executeCommand('workbench.view.extension.voidViewContainer'); + // vscode.commands.executeCommand('vscode.moveViewToPanel', CustomViewProvider.viewId); // move to aux bar - // get the text the user is selecting - const selectionStr = editor.document.getText(editor.selection); + // get the text the user is selecting + const selectionStr = editor.document.getText(editor.selection); - // get the range of the selection - const selectionRange = editor.selection; + // get the range of the selection + const selectionRange = editor.selection; - // get the file the user is in - const filePath = editor.document.uri; + // get the file the user is in + const filePath = editor.document.uri; - // send message to the webview (Sidebar.tsx) - webviewProvider.webview.then(webview => webview.postMessage({ type: 'ctrl+l', selection: { selectionStr, selectionRange, filePath } } satisfies WebviewMessage)); - }) - ); + // send message to the webview (Sidebar.tsx) + webviewProvider.webview.then(webview => webview.postMessage({ type: 'ctrl+l', selection: { selectionStr, selectionRange, filePath } } satisfies WebviewMessage)); + }) + ); - // 3. Show an approve/reject codelens above each change - const approvalCodeLensProvider = new ApprovalCodeLensProvider(); - context.subscriptions.push(vscode.languages.registerCodeLensProvider('*', approvalCodeLensProvider)); + // 3. Show an approve/reject codelens above each change + const approvalCodeLensProvider = new ApprovalCodeLensProvider(); + context.subscriptions.push(vscode.languages.registerCodeLensProvider('*', approvalCodeLensProvider)); - // 4. Add approve/reject commands - context.subscriptions.push(vscode.commands.registerCommand('void.approveDiff', async (params) => { - approvalCodeLensProvider.approveDiff(params) - })); - context.subscriptions.push(vscode.commands.registerCommand('void.discardDiff', async (params) => { - approvalCodeLensProvider.discardDiff(params) - })); + // 4. Add approve/reject commands + context.subscriptions.push(vscode.commands.registerCommand('void.approveDiff', async (params) => { + approvalCodeLensProvider.approveDiff(params) + })); + context.subscriptions.push(vscode.commands.registerCommand('void.discardDiff', async (params) => { + approvalCodeLensProvider.discardDiff(params) + })); - // 5. - webviewProvider.webview.then( - webview => { + // 5. + webviewProvider.webview.then( + webview => { - // when config changes, send it to the sidebar - vscode.workspace.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('void')) { - const apiConfig = getApiConfig() - webview.postMessage({ type: 'apiConfig', apiConfig } satisfies WebviewMessage) - } - }) + // when config changes, send it to the sidebar + vscode.workspace.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('void')) { + const apiConfig = getApiConfig() + webview.postMessage({ type: 'apiConfig', apiConfig } satisfies WebviewMessage) + } + }) - // Receive messages in the extension from the sidebar webview (messages are sent using `postMessage`) - webview.onDidReceiveMessage(async (m: WebviewMessage) => { + // Receive messages in the extension from the sidebar webview (messages are sent using `postMessage`) + webview.onDidReceiveMessage(async (m: WebviewMessage) => { - if (m.type === 'requestFiles') { + if (m.type === 'requestFiles') { - // get contents of all file paths - const files = await Promise.all( - m.filepaths.map(async (filepath) => ({ filepath, content: await readFileContentOfUri(filepath) })) - ) + // get contents of all file paths + const files = await Promise.all( + m.filepaths.map(async (filepath) => ({ filepath, content: await readFileContentOfUri(filepath) })) + ) - // send contents to webview - webview.postMessage({ type: 'files', files, } satisfies WebviewMessage) + // send contents to webview + webview.postMessage({ type: 'files', files, } satisfies WebviewMessage) - } else if (m.type === 'applyCode') { + } else if (m.type === 'applyCode') { - const editor = vscode.window.activeTextEditor - if (!editor) { - vscode.window.showInformationMessage('No active editor!') - return - } - const oldContents = await readFileContentOfUri(editor.document.uri) - const suggestedEdits = getDiffedLines(oldContents, m.code) - await approvalCodeLensProvider.addNewApprovals(editor, suggestedEdits) - } - else if (m.type === 'getApiConfig') { + const editor = vscode.window.activeTextEditor + if (!editor) { + vscode.window.showInformationMessage('No active editor!') + return + } + const oldContents = await readFileContentOfUri(editor.document.uri) + const suggestedEdits = getDiffedLines(oldContents, m.code) + await approvalCodeLensProvider.addNewApprovals(editor, suggestedEdits) + } + else if (m.type === 'getApiConfig') { - const apiConfig = getApiConfig() - console.log('Api config:', apiConfig) + const apiConfig = getApiConfig() + console.log('Api config:', apiConfig) - webview.postMessage({ type: 'apiConfig', apiConfig } satisfies WebviewMessage) + webview.postMessage({ type: 'apiConfig', apiConfig } satisfies WebviewMessage) - } - else { + } + else { - console.error('unrecognized command', m.type, m) - } - }) - } - ) + console.error('unrecognized command', m.type, m) + } + }) + } + ) - // Gets called when user presses ctrl + k (mounts ctrl+k-style codelens) - // TODO need to build this - // const ctrlKCodeLensProvider = new CtrlKCodeLensProvider(); - // context.subscriptions.push(vscode.languages.registerCodeLensProvider('*', ctrlKCodeLensProvider)); - // context.subscriptions.push( - // vscode.commands.registerCommand('void.ctrl+k', () => { - // const editor = vscode.window.activeTextEditor; - // if (!editor) - // return - // ctrlKCodeLensProvider.addNewCodeLens(editor.document, editor.selection); - // // vscode.commands.executeCommand('editor.action.showHover'); // apparently this refreshes the codelenses by having the internals call provideCodeLenses - // }) - // ) + // Gets called when user presses ctrl + k (mounts ctrl+k-style codelens) + // TODO need to build this + // const ctrlKCodeLensProvider = new CtrlKCodeLensProvider(); + // context.subscriptions.push(vscode.languages.registerCodeLensProvider('*', ctrlKCodeLensProvider)); + // context.subscriptions.push( + // vscode.commands.registerCommand('void.ctrl+k', () => { + // const editor = vscode.window.activeTextEditor; + // if (!editor) + // return + // ctrlKCodeLensProvider.addNewCodeLens(editor.document, editor.selection); + // // vscode.commands.executeCommand('editor.action.showHover'); // apparently this refreshes the codelenses by having the internals call provideCodeLenses + // }) + // ) } From d7a3c44e313f98f00c15b43653ccb34ce54d8241 Mon Sep 17 00:00:00 2001 From: Jelf Date: Sat, 21 Sep 2024 14:54:27 +0800 Subject: [PATCH 08/67] chore: eslint --- extensions/void/src/extension.ts | 244 +++++++++++++++---------------- 1 file changed, 122 insertions(+), 122 deletions(-) diff --git a/extensions/void/src/extension.ts b/extensions/void/src/extension.ts index a0bdc9b5..5c050edd 100644 --- a/extensions/void/src/extension.ts +++ b/extensions/void/src/extension.ts @@ -6,163 +6,163 @@ import { SidebarWebviewProvider } from './SidebarWebviewProvider'; import { ApiConfig } from './common/sendLLMMessage'; const readFileContentOfUri = async (uri: vscode.Uri) => { - return Buffer.from(await vscode.workspace.fs.readFile(uri)).toString('utf8').replace(/\r\n/g, '\n'); // must remove windows \r or every line will appear different because of it + return Buffer.from(await vscode.workspace.fs.readFile(uri)).toString('utf8').replace(/\r\n/g, '\n'); // must remove windows \r or every line will appear different because of it } const getApiConfig = () => { - const apiConfig: ApiConfig = { - provider: vscode.workspace.getConfiguration('void').get('provider') ?? 'anthropic', - anthropic: { - model: vscode.workspace.getConfiguration('void').get('anthropic.model') ?? 'claude-3-5-sonnet-20240620', - providerSettings: { - ...vscode.workspace.getConfiguration('void').get('anthropic.providerSettings') ?? {}, - }, - }, - openai: { - model: vscode.workspace.getConfiguration('void').get('openai.model') ?? 'gpt-4o', - providerSettings: { - ...vscode.workspace.getConfiguration('void').get('openai.providerSettings') ?? {}, - }, - }, - azure: { - deploymentId: vscode.workspace.getConfiguration('void').get('azure.deploymentId') ?? '', - providerSettings: { - ...vscode.workspace.getConfiguration('void').get('azure.providerSettings') ?? {}, - }, - }, - ollama: { - model: vscode.workspace.getConfiguration('void').get('ollama.model') ?? 'llama3.1', - providerSettings: { - ...vscode.workspace.getConfiguration('void').get('ollama.providerSettings') ?? {}, - }, - }, - greptile: { - providerSettings: { - ...vscode.workspace.getConfiguration('void').get('greptile.providerSettings') ?? {}, - } - }, - } - console.log(apiConfig); - return apiConfig + const apiConfig: ApiConfig = { + provider: vscode.workspace.getConfiguration('void').get('provider') ?? 'anthropic', + anthropic: { + model: vscode.workspace.getConfiguration('void').get('anthropic.model') ?? 'claude-3-5-sonnet-20240620', + providerSettings: { + ...vscode.workspace.getConfiguration('void').get('anthropic.providerSettings') ?? {}, + }, + }, + openai: { + model: vscode.workspace.getConfiguration('void').get('openai.model') ?? 'gpt-4o', + providerSettings: { + ...vscode.workspace.getConfiguration('void').get('openai.providerSettings') ?? {}, + }, + }, + azure: { + deploymentId: vscode.workspace.getConfiguration('void').get('azure.deploymentId') ?? '', + providerSettings: { + ...vscode.workspace.getConfiguration('void').get('azure.providerSettings') ?? {}, + }, + }, + ollama: { + model: vscode.workspace.getConfiguration('void').get('ollama.model') ?? 'llama3.1', + providerSettings: { + ...vscode.workspace.getConfiguration('void').get('ollama.providerSettings') ?? {}, + }, + }, + greptile: { + providerSettings: { + ...vscode.workspace.getConfiguration('void').get('greptile.providerSettings') ?? {}, + } + }, + } + console.log(apiConfig); + return apiConfig } export function activate(context: vscode.ExtensionContext) { - // 1. Mount the chat sidebar - const webviewProvider = new SidebarWebviewProvider(context); - context.subscriptions.push( - vscode.window.registerWebviewViewProvider(SidebarWebviewProvider.viewId, webviewProvider, { webviewOptions: { retainContextWhenHidden: true } }) - ); + // 1. Mount the chat sidebar + const webviewProvider = new SidebarWebviewProvider(context); + context.subscriptions.push( + vscode.window.registerWebviewViewProvider(SidebarWebviewProvider.viewId, webviewProvider, { webviewOptions: { retainContextWhenHidden: true } }) + ); - // 2. Activate the sidebar on ctrl+l - context.subscriptions.push( - vscode.commands.registerCommand('void.ctrl+l', () => { + // 2. Activate the sidebar on ctrl+l + context.subscriptions.push( + vscode.commands.registerCommand('void.ctrl+l', () => { - const editor = vscode.window.activeTextEditor - if (!editor) - return + const editor = vscode.window.activeTextEditor + if (!editor) + return - // show the sidebar - vscode.commands.executeCommand('workbench.view.extension.voidViewContainer'); - // vscode.commands.executeCommand('vscode.moveViewToPanel', CustomViewProvider.viewId); // move to aux bar + // show the sidebar + vscode.commands.executeCommand('workbench.view.extension.voidViewContainer'); + // vscode.commands.executeCommand('vscode.moveViewToPanel', CustomViewProvider.viewId); // move to aux bar - // get the text the user is selecting - const selectionStr = editor.document.getText(editor.selection); + // get the text the user is selecting + const selectionStr = editor.document.getText(editor.selection); - // get the range of the selection - const selectionRange = editor.selection; + // get the range of the selection + const selectionRange = editor.selection; - // get the file the user is in - const filePath = editor.document.uri; + // get the file the user is in + const filePath = editor.document.uri; - // send message to the webview (Sidebar.tsx) - webviewProvider.webview.then(webview => webview.postMessage({ type: 'ctrl+l', selection: { selectionStr, selectionRange, filePath } } satisfies WebviewMessage)); - }) - ); + // send message to the webview (Sidebar.tsx) + webviewProvider.webview.then(webview => webview.postMessage({ type: 'ctrl+l', selection: { selectionStr, selectionRange, filePath } } satisfies WebviewMessage)); + }) + ); - // 3. Show an approve/reject codelens above each change - const approvalCodeLensProvider = new ApprovalCodeLensProvider(); - context.subscriptions.push(vscode.languages.registerCodeLensProvider('*', approvalCodeLensProvider)); + // 3. Show an approve/reject codelens above each change + const approvalCodeLensProvider = new ApprovalCodeLensProvider(); + context.subscriptions.push(vscode.languages.registerCodeLensProvider('*', approvalCodeLensProvider)); - // 4. Add approve/reject commands - context.subscriptions.push(vscode.commands.registerCommand('void.approveDiff', async (params) => { - approvalCodeLensProvider.approveDiff(params) - })); - context.subscriptions.push(vscode.commands.registerCommand('void.discardDiff', async (params) => { - approvalCodeLensProvider.discardDiff(params) - })); + // 4. Add approve/reject commands + context.subscriptions.push(vscode.commands.registerCommand('void.approveDiff', async (params) => { + approvalCodeLensProvider.approveDiff(params) + })); + context.subscriptions.push(vscode.commands.registerCommand('void.discardDiff', async (params) => { + approvalCodeLensProvider.discardDiff(params) + })); - // 5. - webviewProvider.webview.then( - webview => { + // 5. + webviewProvider.webview.then( + webview => { - // when config changes, send it to the sidebar - vscode.workspace.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('void')) { - const apiConfig = getApiConfig() - webview.postMessage({ type: 'apiConfig', apiConfig } satisfies WebviewMessage) - } - }) + // when config changes, send it to the sidebar + vscode.workspace.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('void')) { + const apiConfig = getApiConfig() + webview.postMessage({ type: 'apiConfig', apiConfig } satisfies WebviewMessage) + } + }) - // Receive messages in the extension from the sidebar webview (messages are sent using `postMessage`) - webview.onDidReceiveMessage(async (m: WebviewMessage) => { + // Receive messages in the extension from the sidebar webview (messages are sent using `postMessage`) + webview.onDidReceiveMessage(async (m: WebviewMessage) => { - if (m.type === 'requestFiles') { + if (m.type === 'requestFiles') { - // get contents of all file paths - const files = await Promise.all( - m.filepaths.map(async (filepath) => ({ filepath, content: await readFileContentOfUri(filepath) })) - ) + // get contents of all file paths + const files = await Promise.all( + m.filepaths.map(async (filepath) => ({ filepath, content: await readFileContentOfUri(filepath) })) + ) - // send contents to webview - webview.postMessage({ type: 'files', files, } satisfies WebviewMessage) + // send contents to webview + webview.postMessage({ type: 'files', files, } satisfies WebviewMessage) - } else if (m.type === 'applyCode') { + } else if (m.type === 'applyCode') { - const editor = vscode.window.activeTextEditor - if (!editor) { - vscode.window.showInformationMessage('No active editor!') - return - } - const oldContents = await readFileContentOfUri(editor.document.uri) - const suggestedEdits = getDiffedLines(oldContents, m.code) - await approvalCodeLensProvider.addNewApprovals(editor, suggestedEdits) - } - else if (m.type === 'getApiConfig') { + const editor = vscode.window.activeTextEditor + if (!editor) { + vscode.window.showInformationMessage('No active editor!') + return + } + const oldContents = await readFileContentOfUri(editor.document.uri) + const suggestedEdits = getDiffedLines(oldContents, m.code) + await approvalCodeLensProvider.addNewApprovals(editor, suggestedEdits) + } + else if (m.type === 'getApiConfig') { - const apiConfig = getApiConfig() - console.log('Api config:', apiConfig) + const apiConfig = getApiConfig() + console.log('Api config:', apiConfig) - webview.postMessage({ type: 'apiConfig', apiConfig } satisfies WebviewMessage) + webview.postMessage({ type: 'apiConfig', apiConfig } satisfies WebviewMessage) - } - else { + } + else { - console.error('unrecognized command', m.type, m) - } - }) - } - ) + console.error('unrecognized command', m.type, m) + } + }) + } + ) - // Gets called when user presses ctrl + k (mounts ctrl+k-style codelens) - // TODO need to build this - // const ctrlKCodeLensProvider = new CtrlKCodeLensProvider(); - // context.subscriptions.push(vscode.languages.registerCodeLensProvider('*', ctrlKCodeLensProvider)); - // context.subscriptions.push( - // vscode.commands.registerCommand('void.ctrl+k', () => { - // const editor = vscode.window.activeTextEditor; - // if (!editor) - // return - // ctrlKCodeLensProvider.addNewCodeLens(editor.document, editor.selection); - // // vscode.commands.executeCommand('editor.action.showHover'); // apparently this refreshes the codelenses by having the internals call provideCodeLenses - // }) - // ) + // Gets called when user presses ctrl + k (mounts ctrl+k-style codelens) + // TODO need to build this + // const ctrlKCodeLensProvider = new CtrlKCodeLensProvider(); + // context.subscriptions.push(vscode.languages.registerCodeLensProvider('*', ctrlKCodeLensProvider)); + // context.subscriptions.push( + // vscode.commands.registerCommand('void.ctrl+k', () => { + // const editor = vscode.window.activeTextEditor; + // if (!editor) + // return + // ctrlKCodeLensProvider.addNewCodeLens(editor.document, editor.selection); + // // vscode.commands.executeCommand('editor.action.showHover'); // apparently this refreshes the codelenses by having the internals call provideCodeLenses + // }) + // ) } From bf9029f9bc311c778b0c010016177183b6c1807f Mon Sep 17 00:00:00 2001 From: Jelf Date: Sat, 21 Sep 2024 14:56:15 +0800 Subject: [PATCH 09/67] chore: remove console.log --- extensions/void/src/extension.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/void/src/extension.ts b/extensions/void/src/extension.ts index 5c050edd..0b218bcd 100644 --- a/extensions/void/src/extension.ts +++ b/extensions/void/src/extension.ts @@ -43,7 +43,6 @@ const getApiConfig = () => { } }, } - console.log(apiConfig); return apiConfig } From 1beb011bfa2d9e7ce7ebb6241fa75f0b40839865 Mon Sep 17 00:00:00 2001 From: Jelf Date: Sat, 21 Sep 2024 15:45:36 +0800 Subject: [PATCH 10/67] refactor: configuration --- extensions/void/package.json | 397 +++++++++--------- extensions/void/src/SidebarWebviewProvider.ts | 8 +- extensions/void/src/common/sendLLMMessage.ts | 31 +- extensions/void/src/extension.ts | 6 + 4 files changed, 221 insertions(+), 221 deletions(-) diff --git a/extensions/void/package.json b/extensions/void/package.json index 4d6d54b0..972e1d3d 100644 --- a/extensions/void/package.json +++ b/extensions/void/package.json @@ -27,252 +27,233 @@ "ollama" ] }, - "void.anthropic": { + "void.anthropic.apiKey": { + "type": "string", + "description": "Anthropic API key." + }, + "void.anthropic.model": { + "type": "string", + "default": "claude-3-5-sonnet-20240620", + "description": "Choose a model ('claude-3-5-sonnet-20240620' | 'claude-3-opus-20240229' | 'claude-3-sonnet-20240229' | 'claude-3-haiku-20240307')", + "enum": [ + "claude-3-5-sonnet-20240620", + "claude-3-opus-20240229", + "claude-3-sonnet-20240229", + "claude-3-haiku-20240307" + ] + }, + "void.anthropic.providerSettings": { "type": "object", "properties": { - "model": { + "baseURL": { "type": "string", - "default": "claude-3-5-sonnet-20240620", - "description": "Choose a model ('claude-3-5-sonnet-20240620' | 'claude-3-opus-20240229' | 'claude-3-sonnet-20240229' | 'claude-3-haiku-20240307')", - "enum": [ - "claude-3-5-sonnet-20240620", - "claude-3-opus-20240229", - "claude-3-sonnet-20240229", - "claude-3-haiku-20240307" - ] + "description": "Use a different URL prefix for API calls, e.g. to use proxy servers." }, - "providerSettings": { + "apiKey": {}, + "headers": { "type": "object", - "properties": { - "baseURL": { - "type": "string", - "description": "Use a different URL prefix for API calls, e.g. to use proxy servers." - }, - "apiKey": { - "type": "string", - "description": "Anthropic API key." - }, - "headers": { - "type": "object", - "description": "Custom headers to include in the requests." - } - } + "description": "Custom headers to include in the requests." } } }, - "void.openai": { + "void.openai.model": { + "type": "string", + "default": "gpt-4o", + "description": "Choose a model ('o1-preview' | 'o1-mini' | 'gpt-4o' | 'gpt-4o-2024-05-13' | 'gpt-4o-2024-08-06' | 'gpt-4o-mini' | 'gpt-4o-mini-2024-07-18' | 'gpt-4-turbo' | 'gpt-4-turbo-2024-04-09' | 'gpt-4-turbo-preview' | 'gpt-4-0125-preview' | 'gpt-4-1106-preview' | 'gpt-4' | 'gpt-4-0613' | 'gpt-3.5-turbo-0125' | 'gpt-3.5-turbo' | 'gpt-3.5-turbo-1106')", + "enum": [ + "o1-preview", + "o1-mini", + "gpt-4o", + "gpt-4o-2024-05-13", + "gpt-4o-2024-08-06", + "gpt-4o-mini", + "gpt-4o-mini-2024-07-18", + "gpt-4-turbo", + "gpt-4-turbo-2024-04-09", + "gpt-4-turbo-preview", + "gpt-4-0125-preview", + "gpt-4-1106-preview", + "gpt-4", + "gpt-4-0613", + "gpt-3.5-turbo-0125", + "gpt-3.5-turbo", + "gpt-3.5-turbo-1106" + ] + }, + "void.openai.apiKey": { + "type": "string", + "description": "OpenAI API key." + }, + "void.openai.providerSettings": { "type": "object", "properties": { - "model": { + "baseURL": { "type": "string", - "default": "gpt-4o", - "description": "Choose a model ('o1-preview' | 'o1-mini' | 'gpt-4o' | 'gpt-4o-2024-05-13' | 'gpt-4o-2024-08-06' | 'gpt-4o-mini' | 'gpt-4o-mini-2024-07-18' | 'gpt-4-turbo' | 'gpt-4-turbo-2024-04-09' | 'gpt-4-turbo-preview' | 'gpt-4-0125-preview' | 'gpt-4-1106-preview' | 'gpt-4' | 'gpt-4-0613' | 'gpt-3.5-turbo-0125' | 'gpt-3.5-turbo' | 'gpt-3.5-turbo-1106')", - "enum": [ - "o1-preview", - "o1-mini", - "gpt-4o", - "gpt-4o-2024-05-13", - "gpt-4o-2024-08-06", - "gpt-4o-mini", - "gpt-4o-mini-2024-07-18", - "gpt-4-turbo", - "gpt-4-turbo-2024-04-09", - "gpt-4-turbo-preview", - "gpt-4-0125-preview", - "gpt-4-1106-preview", - "gpt-4", - "gpt-4-0613", - "gpt-3.5-turbo-0125", - "gpt-3.5-turbo", - "gpt-3.5-turbo-1106" - ] + "description": "Use a different URL prefix for API calls, e.g. to use proxy servers." }, - "providerSettings": { + "headers": { "type": "object", - "properties": { - "baseURL": { - "type": "string", - "description": "Use a different URL prefix for API calls, e.g. to use proxy servers." - }, - "apiKey": { - "type": "string", - "description": "OpenAI API key." - }, - "headers": { - "type": "object", - "description": "Custom headers to include in the requests." - }, - "organization": { - "type": "string", - "description": "OpenAI Organization." - }, - "project": { - "type": "string", - "description": "OpenAI project." - }, - "compatibility": { - "type": "string", - "description": "OpenAI compatibility mode. Should be set to `strict` when using the OpenAI API, and `compatible` when using 3rd party providers. In `compatible` mode, newer information such as streamOptions are not being sent. Defaults to 'compatible'.", - "enum": [ - "strict", - "compatible" - ] - } - } + "description": "Custom headers to include in the requests." + }, + "organization": { + "type": "string", + "description": "OpenAI Organization." + }, + "project": { + "type": "string", + "description": "OpenAI project." + }, + "compatibility": { + "type": "string", + "description": "OpenAI compatibility mode. Should be set to `strict` when using the OpenAI API, and `compatible` when using 3rd party providers. In `compatible` mode, newer information such as streamOptions are not being sent. Defaults to 'compatible'.", + "enum": [ + "strict", + "compatible" + ] } } }, - "void.azure": { + "void.azure.deploymentId": { + "type": "string", + "description": "Azure API deployment ID." + }, + "void.azure.resourceName": { + "type": "string", + "description": "Name of the Azure OpenAI resource. Either this or `baseURL` can be used. \nThe resource name is used in the assembled URL: `https://{resourceName}.openai.azure.com/openai/deployments/{modelId}{path}`" + }, + "void.azure.apiKey": { + "type": "string", + "description": "Azure API key." + }, + "void.azure.providerSettings": { "type": "object", "properties": { - "deploymentId": { + "baseURL": { "type": "string", - "description": "Azure API deployment ID." + "description": "Azure API base URL." }, - "providerSettings": { + "headers": { "type": "object", - "properties": { - "apiKey": { - "type": "string", - "description": "Azure API key." - }, - "baseURL": { - "type": "string", - "description": "Azure API base URL." - }, - "resourceName": { - "type": "string", - "description": "Name of the Azure OpenAI resource. Either this or `baseURL` can be used. \nThe resource name is used in the assembled URL: `https://{resourceName}.openai.azure.com/openai/deployments/{modelId}{path}`" - }, - "headers": { - "type": "object", - "description": "Custom headers to include in the requests." - } - } + "description": "Custom headers to include in the requests." } } }, - "void.ollama": { + "void.ollama.model": { + "type": "string", + "description": "Ollama model. 'codegemma' | 'codegemma:2b' | 'codegemma:7b' | 'codellama' | 'codellama:7b' | 'codellama:13b' | 'codellama:34b' | 'codellama:70b' | 'codellama:code' | 'codellama:python' | 'command-r' | 'command-r:35b' | 'command-r-plus' | 'command-r-plus:104b' | 'deepseek-coder-v2' | 'deepseek-coder-v2:16b' | 'deepseek-coder-v2:236b' | 'falcon2' | 'falcon2:11b' | 'firefunction-v2' | 'firefunction-v2:70b' | 'gemma' | 'gemma:2b' | 'gemma:7b' | 'gemma2' | 'gemma2:2b' | 'gemma2:9b' | 'gemma2:27b' | 'llama2' | 'llama2:7b' | 'llama2:13b' | 'llama2:70b' | 'llama3' | 'llama3:8b' | 'llama3:70b' | 'llama3-chatqa' | 'llama3-chatqa:8b' | 'llama3-chatqa:70b' | 'llama3-gradient' | 'llama3-gradient:8b' | 'llama3-gradient:70b' | 'llama3.1' | 'llama3.1:8b' | 'llama3.1:70b' | 'llama3.1:405b' | 'llava' | 'llava:7b' | 'llava:13b' | 'llava:34b' | 'llava-llama3' | 'llava-llama3:8b' | 'llava-phi3' | 'llava-phi3:3.8b' | 'mistral' | 'mistral:7b' | 'mistral-large' | 'mistral-large:123b' | 'mistral-nemo' | 'mistral-nemo:12b' | 'mixtral' | 'mixtral:8x7b' | 'mixtral:8x22b' | 'moondream' | 'moondream:1.8b' | 'openhermes' | 'openhermes:v2.5' | 'phi3' | 'phi3:3.8b' | 'phi3:14b' | 'phi3.5' | 'phi3.5:3.8b' | 'qwen' | 'qwen:7b' | 'qwen:14b' | 'qwen:32b' | 'qwen:72b' | 'qwen:110b' | 'qwen2' | 'qwen2:0.5b' | 'qwen2:1.5b' | 'qwen2:7b' | 'qwen2:72b' | 'smollm' | 'smollm:135m' | 'smollm:360m' | 'smollm:1.7b'", + "default": "llama3.1", + "enum": [ + "codegemma", + "codegemma:2b", + "codegemma:7b", + "codellama", + "codellama:7b", + "codellama:13b", + "codellama:34b", + "codellama:70b", + "codellama:code", + "codellama:python", + "command-r", + "command-r:35b", + "command-r-plus", + "command-r-plus:104b", + "deepseek-coder-v2", + "deepseek-coder-v2:16b", + "deepseek-coder-v2:236b", + "falcon2", + "falcon2:11b", + "firefunction-v2", + "firefunction-v2:70b", + "gemma", + "gemma:2b", + "gemma:7b", + "gemma2", + "gemma2:2b", + "gemma2:9b", + "gemma2:27b", + "llama2", + "llama2:7b", + "llama2:13b", + "llama2:70b", + "llama3", + "llama3:8b", + "llama3:70b", + "llama3-chatqa", + "llama3-chatqa:8b", + "llama3-chatqa:70b", + "llama3-gradient", + "llama3-gradient:8b", + "llama3-gradient:70b", + "llama3.1", + "llama3.1:8b", + "llama3.1:70b", + "llama3.1:405b", + "llava", + "llava:7b", + "llava:13b", + "llava:34b", + "llava-llama3", + "llava-llama3:8b", + "llava-phi3", + "llava-phi3:3.8b", + "mistral", + "mistral:7b", + "mistral-large", + "mistral-large:123b", + "mistral-nemo", + "mistral-nemo:12b", + "mixtral", + "mixtral:8x7b", + "mixtral:8x22b", + "moondream", + "moondream:1.8b", + "openhermes", + "openhermes:v2.5", + "phi3", + "phi3:3.8b", + "phi3:14b", + "phi3.5", + "phi3.5:3.8b", + "qwen", + "qwen:7b", + "qwen:14b", + "qwen:32b", + "qwen:72b", + "qwen:110b", + "qwen2", + "qwen2:0.5b", + "qwen2:1.5b", + "qwen2:7b", + "qwen2:72b", + "smollm", + "smollm:135m", + "smollm:360m", + "smollm:1.7b" + ] + }, + "void.ollama.providerSettings": { "type": "object", "properties": { - "model": { + "baseURL": { "type": "string", - "description": "Ollama model. 'codegemma' | 'codegemma:2b' | 'codegemma:7b' | 'codellama' | 'codellama:7b' | 'codellama:13b' | 'codellama:34b' | 'codellama:70b' | 'codellama:code' | 'codellama:python' | 'command-r' | 'command-r:35b' | 'command-r-plus' | 'command-r-plus:104b' | 'deepseek-coder-v2' | 'deepseek-coder-v2:16b' | 'deepseek-coder-v2:236b' | 'falcon2' | 'falcon2:11b' | 'firefunction-v2' | 'firefunction-v2:70b' | 'gemma' | 'gemma:2b' | 'gemma:7b' | 'gemma2' | 'gemma2:2b' | 'gemma2:9b' | 'gemma2:27b' | 'llama2' | 'llama2:7b' | 'llama2:13b' | 'llama2:70b' | 'llama3' | 'llama3:8b' | 'llama3:70b' | 'llama3-chatqa' | 'llama3-chatqa:8b' | 'llama3-chatqa:70b' | 'llama3-gradient' | 'llama3-gradient:8b' | 'llama3-gradient:70b' | 'llama3.1' | 'llama3.1:8b' | 'llama3.1:70b' | 'llama3.1:405b' | 'llava' | 'llava:7b' | 'llava:13b' | 'llava:34b' | 'llava-llama3' | 'llava-llama3:8b' | 'llava-phi3' | 'llava-phi3:3.8b' | 'mistral' | 'mistral:7b' | 'mistral-large' | 'mistral-large:123b' | 'mistral-nemo' | 'mistral-nemo:12b' | 'mixtral' | 'mixtral:8x7b' | 'mixtral:8x22b' | 'moondream' | 'moondream:1.8b' | 'openhermes' | 'openhermes:v2.5' | 'phi3' | 'phi3:3.8b' | 'phi3:14b' | 'phi3.5' | 'phi3.5:3.8b' | 'qwen' | 'qwen:7b' | 'qwen:14b' | 'qwen:32b' | 'qwen:72b' | 'qwen:110b' | 'qwen2' | 'qwen2:0.5b' | 'qwen2:1.5b' | 'qwen2:7b' | 'qwen2:72b' | 'smollm' | 'smollm:135m' | 'smollm:360m' | 'smollm:1.7b'", - "default": "llama3.1", - "enum": [ - "codegemma", - "codegemma:2b", - "codegemma:7b", - "codellama", - "codellama:7b", - "codellama:13b", - "codellama:34b", - "codellama:70b", - "codellama:code", - "codellama:python", - "command-r", - "command-r:35b", - "command-r-plus", - "command-r-plus:104b", - "deepseek-coder-v2", - "deepseek-coder-v2:16b", - "deepseek-coder-v2:236b", - "falcon2", - "falcon2:11b", - "firefunction-v2", - "firefunction-v2:70b", - "gemma", - "gemma:2b", - "gemma:7b", - "gemma2", - "gemma2:2b", - "gemma2:9b", - "gemma2:27b", - "llama2", - "llama2:7b", - "llama2:13b", - "llama2:70b", - "llama3", - "llama3:8b", - "llama3:70b", - "llama3-chatqa", - "llama3-chatqa:8b", - "llama3-chatqa:70b", - "llama3-gradient", - "llama3-gradient:8b", - "llama3-gradient:70b", - "llama3.1", - "llama3.1:8b", - "llama3.1:70b", - "llama3.1:405b", - "llava", - "llava:7b", - "llava:13b", - "llava:34b", - "llava-llama3", - "llava-llama3:8b", - "llava-phi3", - "llava-phi3:3.8b", - "mistral", - "mistral:7b", - "mistral-large", - "mistral-large:123b", - "mistral-nemo", - "mistral-nemo:12b", - "mixtral", - "mixtral:8x7b", - "mixtral:8x22b", - "moondream", - "moondream:1.8b", - "openhermes", - "openhermes:v2.5", - "phi3", - "phi3:3.8b", - "phi3:14b", - "phi3.5", - "phi3.5:3.8b", - "qwen", - "qwen:7b", - "qwen:14b", - "qwen:32b", - "qwen:72b", - "qwen:110b", - "qwen2", - "qwen2:0.5b", - "qwen2:1.5b", - "qwen2:7b", - "qwen2:72b", - "smollm", - "smollm:135m", - "smollm:360m", - "smollm:1.7b" - ] + "description": "Ollama base URL. Local API server can be started with `OLLAMA_ORIGINS=* ollama serve`" }, - "providerSettings": { + "headers": { "type": "object", - "properties": { - "baseURL": { - "type": "string", - "description": "Ollama base URL. Local API server can be started with `OLLAMA_ORIGINS=* ollama serve`" - }, - "headers": { - "type": "object", - "description": "Custom headers to include in the requests." - } - } + "description": "Custom headers to include in the requests." } } }, + "void.greptile.apiKey": { + "type": "string", + "description": "Greptile API key." + }, "void.greptile": { "type": "object", "properties": { "providerSettings": { "type": "object", "properties": { - "apikey": { - "type": "string", - "description": "Greptile API key." - }, "headers": { "type": "object", "description": "Custom headers to include in the requests.", diff --git a/extensions/void/src/SidebarWebviewProvider.ts b/extensions/void/src/SidebarWebviewProvider.ts index c95e1e12..c1533cca 100644 --- a/extensions/void/src/SidebarWebviewProvider.ts +++ b/extensions/void/src/SidebarWebviewProvider.ts @@ -57,11 +57,11 @@ export class SidebarWebviewProvider implements vscode.WebviewViewProvider { const ollamaBaseURL = new URL('/', vscode.workspace.getConfiguration('void').get('ollama.providerSettings.baseURL') || 'http://127.0.0.1:11434').toString() - const azureProviderSettings = vscode.workspace.getConfiguration('void').get('azure.providerSettings') as AzureOpenAIProviderSettings - const azureBaseURL = new URL('/', vscode.workspace.getConfiguration('void').get('azure.providerSettings.baseURL') || `https://${azureProviderSettings.resourceName}.openai.azure.com/openai/deployments`).toString() + const azureResourceName = vscode.workspace.getConfiguration('void').get('azure.resourceName') ?? '' + const azureBaseURL = new URL('/', vscode.workspace.getConfiguration('void').get('azure.providerSettings.baseURL') || `https://${azureResourceName}.openai.azure.com/openai/deployments`).toString() const openaiBaseURL = new URL('/', vscode.workspace.getConfiguration('void').get('openai.providerSettings.baseURL') || 'https://api.openai.com').toString() - const greptileBaseURL = new URL('/', vscode.workspace.getConfiguration('void').get('greptile.providerSettings.baseURL') || 'https://api.mistral.ai').toString() - const anthropicBaseURL = new URL('/', vscode.workspace.getConfiguration('void').get('anthropic.providerSettings.baseURL') || 'https://api.mistral.ai').toString() + const greptileBaseURL = new URL('/', vscode.workspace.getConfiguration('void').get('greptile.providerSettings.baseURL') || 'https://api.greptile.com').toString() + const anthropicBaseURL = new URL('/', vscode.workspace.getConfiguration('void').get('anthropic.providerSettings.baseURL') || 'https://api.anthropic.com').toString() const allowed_urls = [ ollamaBaseURL, azureBaseURL, diff --git a/extensions/void/src/common/sendLLMMessage.ts b/extensions/void/src/common/sendLLMMessage.ts index 256f2ca2..479a61d3 100644 --- a/extensions/void/src/common/sendLLMMessage.ts +++ b/extensions/void/src/common/sendLLMMessage.ts @@ -10,21 +10,24 @@ export type ApiConfig = { anthropic: { /** @default 'claude-3-5-sonnet-20240620' */ model: string, - providerSettings?: AnthropicProviderSettings + apiKey: string, + providerSettings?: Omit }, openai: { /** @default 'gpt-4o' */ model: string, - providerSettings?: OpenAIProviderSettings + apiKey: string, + providerSettings?: Omit }, azure: { deploymentId: string, - providerSettings?: AzureOpenAIProviderSettings + resourceName: string, + apiKey: string, + providerSettings?: Omit }, greptile: { + apiKey: string, providerSettings?: { - apikey?: string, - githubPAT?: string, headers?: Record, repoinfo?: { remote?: string, // e.g. 'github' @@ -83,7 +86,7 @@ const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFin fetch('https://api.greptile.com/v2/query', { method: 'POST', headers: { - "Authorization": `Bearer ${apiConfig.greptile.providerSettings?.apikey}`, + "Authorization": `Bearer ${apiConfig.greptile.apiKey}`, "Content-Type": `application/json`, ...apiConfig.greptile.providerSettings?.headers }, @@ -164,10 +167,20 @@ export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({ messages, onText, export const getAiModel = (apiConfig: ApiConfig) => { switch (apiConfig.provider) { - case 'openai': return createOpenAI(apiConfig.openai.providerSettings)(apiConfig.openai.model || 'gpt-4o') - case 'anthropic': return createAnthropic(apiConfig.anthropic.providerSettings)(apiConfig.anthropic.model || 'claude-3-5-sonnet-20240620') + case 'openai': return createOpenAI({ + ...apiConfig.openai.providerSettings, + apiKey: apiConfig.openai.apiKey, + })(apiConfig.openai.model || 'gpt-4o') + case 'anthropic': return createAnthropic({ + ...apiConfig.anthropic.providerSettings, + apiKey: apiConfig.anthropic.apiKey, + })(apiConfig.anthropic.model || 'claude-3-5-sonnet-20240620') case 'ollama': return createOllama(apiConfig.ollama.providerSettings)(apiConfig.ollama.model || 'llama3.1') - case 'azure': return createAzure(apiConfig.azure.providerSettings)(`${apiConfig.azure.deploymentId}`) + case 'azure': return createAzure({ + ...apiConfig.azure.providerSettings, + apiKey: apiConfig.azure.apiKey, + resourceName: apiConfig.azure.resourceName, + })(`${apiConfig.azure.deploymentId}`) default: throw new Error(`Error: provider was ${apiConfig.provider}, which is not recognized!`) } diff --git a/extensions/void/src/extension.ts b/extensions/void/src/extension.ts index 0b218bcd..5d2ae6c0 100644 --- a/extensions/void/src/extension.ts +++ b/extensions/void/src/extension.ts @@ -15,18 +15,22 @@ const getApiConfig = () => { provider: vscode.workspace.getConfiguration('void').get('provider') ?? 'anthropic', anthropic: { model: vscode.workspace.getConfiguration('void').get('anthropic.model') ?? 'claude-3-5-sonnet-20240620', + apiKey: vscode.workspace.getConfiguration('void').get('anthropic.apiKey') ?? '', providerSettings: { ...vscode.workspace.getConfiguration('void').get('anthropic.providerSettings') ?? {}, }, }, openai: { model: vscode.workspace.getConfiguration('void').get('openai.model') ?? 'gpt-4o', + apiKey: vscode.workspace.getConfiguration('void').get('openai.apiKey') ?? '', providerSettings: { ...vscode.workspace.getConfiguration('void').get('openai.providerSettings') ?? {}, }, }, azure: { deploymentId: vscode.workspace.getConfiguration('void').get('azure.deploymentId') ?? '', + resourceName: vscode.workspace.getConfiguration('void').get('azure.resourceName') ?? '', + apiKey: vscode.workspace.getConfiguration('void').get('azure.apiKey') ?? '', providerSettings: { ...vscode.workspace.getConfiguration('void').get('azure.providerSettings') ?? {}, }, @@ -38,11 +42,13 @@ const getApiConfig = () => { }, }, greptile: { + apiKey: vscode.workspace.getConfiguration('void').get('greptile.apiKey') ?? '', providerSettings: { ...vscode.workspace.getConfiguration('void').get('greptile.providerSettings') ?? {}, } }, } + return apiConfig } From 4f9b5ac873977521b3347784536eabf735d8edeb Mon Sep 17 00:00:00 2001 From: Jelf Date: Sat, 21 Sep 2024 15:47:48 +0800 Subject: [PATCH 11/67] chore: rm --- extensions/void/src/SidebarWebviewProvider.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/void/src/SidebarWebviewProvider.ts b/extensions/void/src/SidebarWebviewProvider.ts index c1533cca..13a28b9a 100644 --- a/extensions/void/src/SidebarWebviewProvider.ts +++ b/extensions/void/src/SidebarWebviewProvider.ts @@ -1,6 +1,5 @@ // renders the code from `src/sidebar` -import { AzureOpenAIProviderSettings } from '@ai-sdk/azure'; import * as vscode from 'vscode'; function getNonce() { From 44bc8bfdbf12865dd340a9719ccef79dc57e842a Mon Sep 17 00:00:00 2001 From: Jelf Date: Sat, 21 Sep 2024 16:09:28 +0800 Subject: [PATCH 12/67] chore: update --- extensions/void/package.json | 74 +++++++++++++++++------------------- 1 file changed, 35 insertions(+), 39 deletions(-) diff --git a/extensions/void/package.json b/extensions/void/package.json index 972e1d3d..4bc55642 100644 --- a/extensions/void/package.json +++ b/extensions/void/package.json @@ -49,13 +49,16 @@ "type": "string", "description": "Use a different URL prefix for API calls, e.g. to use proxy servers." }, - "apiKey": {}, "headers": { "type": "object", "description": "Custom headers to include in the requests." } } }, + "void.openai.apiKey": { + "type": "string", + "description": "OpenAI API key." + }, "void.openai.model": { "type": "string", "default": "gpt-4o", @@ -80,10 +83,6 @@ "gpt-3.5-turbo-1106" ] }, - "void.openai.apiKey": { - "type": "string", - "description": "OpenAI API key." - }, "void.openai.providerSettings": { "type": "object", "properties": { @@ -113,6 +112,10 @@ } } }, + "void.azure.apiKey": { + "type": "string", + "description": "Azure API key." + }, "void.azure.deploymentId": { "type": "string", "description": "Azure API deployment ID." @@ -121,15 +124,12 @@ "type": "string", "description": "Name of the Azure OpenAI resource. Either this or `baseURL` can be used. \nThe resource name is used in the assembled URL: `https://{resourceName}.openai.azure.com/openai/deployments/{modelId}{path}`" }, - "void.azure.apiKey": { - "type": "string", - "description": "Azure API key." - }, "void.azure.providerSettings": { "type": "object", "properties": { "baseURL": { "type": "string", + "default": "https://${resourceName}.openai.azure.com/openai/deployments", "description": "Azure API base URL." }, "headers": { @@ -236,6 +236,7 @@ "properties": { "baseURL": { "type": "string", + "default": "http://127.0.0.1:11434", "description": "Ollama base URL. Local API server can be started with `OLLAMA_ORIGINS=* ollama serve`" }, "headers": { @@ -248,40 +249,35 @@ "type": "string", "description": "Greptile API key." }, - "void.greptile": { + "void.greptile.providerSettings": { "type": "object", "properties": { - "providerSettings": { + "headers": { "type": "object", + "description": "Custom headers to include in the requests.", "properties": { - "headers": { - "type": "object", - "description": "Custom headers to include in the requests.", - "properties": { - "X-Github-Token": { - "type": "string", - "description": "GitHub PAT." - } - } - }, - "repoinfo": { - "type": "array", - "items": { - "type": "object", - "properties": { - "remote": { - "type": "string", - "description": "GitHub remote." - }, - "repository": { - "type": "string", - "description": "GitHub repository." - }, - "branch": { - "type": "string", - "description": "GitHub branch." - } - } + "X-Github-Token": { + "type": "string", + "description": "GitHub PAT." + } + } + }, + "repoinfo": { + "type": "array", + "items": { + "type": "object", + "properties": { + "remote": { + "type": "string", + "description": "GitHub remote." + }, + "repository": { + "type": "string", + "description": "GitHub repository." + }, + "branch": { + "type": "string", + "description": "GitHub branch." } } } From 5e5fe8090175a7bcf6ede71ea697e698087bc5c0 Mon Sep 17 00:00:00 2001 From: w1gs Date: Sat, 21 Sep 2024 04:56:41 -0400 Subject: [PATCH 13/67] allowed urls updating when configuration changes --- extensions/void/src/SidebarWebviewProvider.ts | 65 +++++++++++++------ extensions/void/src/common/sendLLMMessage.ts | 23 +++++-- extensions/void/src/extension.ts | 7 +- extensions/void/src/shared_types.ts | 3 + extensions/void/src/sidebar/Sidebar.tsx | 7 +- extensions/void/src/sidebar/getVscodeApi.ts | 3 +- 6 files changed, 80 insertions(+), 28 deletions(-) diff --git a/extensions/void/src/SidebarWebviewProvider.ts b/extensions/void/src/SidebarWebviewProvider.ts index 46c45085..0d85fd7f 100644 --- a/extensions/void/src/SidebarWebviewProvider.ts +++ b/extensions/void/src/SidebarWebviewProvider.ts @@ -18,6 +18,8 @@ export class SidebarWebviewProvider implements vscode.WebviewViewProvider { private readonly _extensionUri: vscode.Uri private _res: (c: vscode.Webview) => void // used to resolve the webview + private allowed_urls: string[] = ['https://api.anthropic.com', 'https://api.openai.com', 'https://api.greptile.com', 'http://localhost:11434']; + private _webviewView?: vscode.WebviewView; constructor(context: vscode.ExtensionContext) { // const extensionPath = context.extensionPath // the directory where the extension is installed, might be useful later, not sure for what though... was included in webviewProvider code @@ -27,6 +29,45 @@ export class SidebarWebviewProvider implements vscode.WebviewViewProvider { this.webview = new Promise((res, rej) => { temp_res = res }) if (!temp_res) throw new Error("sidebar provider: resolver was undefined") this._res = temp_res + + vscode.workspace.onDidChangeConfiguration(event => { + if (event.affectsConfiguration('void.ollamaSettings.endpoint')) { + this.updateAllowedUrls(); + // Regenerate the webview's HTML content + if (this._webviewView) { + this._webviewView.webview.html = this.getWebviewContent(this._webviewView.webview); + } + } + }); + } + + private getWebviewContent(webview: vscode.Webview): string { + 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')); + const rootUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri)); + const nonce = getNonce(); + + const allowed_urls = this.allowed_urls; + return ` + + + + + Custom View + + + + + +
+ + + `; + } + + private updateAllowedUrls() { + const ollamaEndpoint: string = vscode.workspace.getConfiguration('void').get('ollamaSettings.endpoint') || 'http://localhost:11434'; + this.allowed_urls = ['https://api.anthropic.com', 'https://api.openai.com', 'https://api.greptile.com', ollamaEndpoint]; } // called internally by vscode @@ -35,6 +76,7 @@ export class SidebarWebviewProvider implements vscode.WebviewViewProvider { context: vscode.WebviewViewResolveContext, token: vscode.CancellationToken, ) { + this._webviewView = webviewView; const webview = webviewView.webview @@ -54,25 +96,10 @@ export class SidebarWebviewProvider implements vscode.WebviewViewProvider { const nonce = getNonce(); // only scripts with the nonce are allowed to run, this is a recommended security measure - // Allow Ollama endpoint - const ollamaEndpoint = vscode.workspace.getConfiguration('void').get('ollamaSettings.endpoint') || 'http://localhost:11434' - const allowed_urls = ['https://api.anthropic.com', 'https://api.openai.com', 'https://api.greptile.com', ollamaEndpoint ] - webview.html = ` - - - - - Custom View - - - - - -
- - - `; - + // Regenerate the Sidebar html whenever the allowed_urls changes + this.updateAllowedUrls(); + const allowed_urls = this.allowed_urls; + webview.html = this.getWebviewContent(webview); this._res(webview); } diff --git a/extensions/void/src/common/sendLLMMessage.ts b/extensions/void/src/common/sendLLMMessage.ts index 2fb3dd3d..de39feaa 100644 --- a/extensions/void/src/common/sendLLMMessage.ts +++ b/extensions/void/src/common/sendLLMMessage.ts @@ -1,6 +1,8 @@ import Anthropic from '@anthropic-ai/sdk'; import OpenAI from 'openai'; import { Ollama } from 'ollama/browser' +import { getVSCodeAPI } from '../sidebar/getVscodeApi'; + export type ApiConfig = { anthropic: { @@ -220,9 +222,21 @@ const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFin export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({ messages, onText, onFinalMessage, apiConfig }) => { if (!apiConfig) return { abort: () => { } } - const whichApi = apiConfig.whichApi; + if ( + apiConfig.anthropic.apikey === "" && + apiConfig.greptile.apikey === "" && + apiConfig.openai.apikey === "" && + apiConfig.ollama.endpoint === "" && + apiConfig.ollama.model === "" + ) { + getVSCodeAPI().postMessage({ type: 'displayError', message: 'Required API keys are not set.' }) + onFinalMessage("Required API keys are not set."); + return { abort: () => { }} + } - switch (whichApi) { + + + switch (apiConfig.whichApi) { case 'anthropic': return sendClaudeMsg({ messages, onText, onFinalMessage, apiConfig }); case 'openai': @@ -232,8 +246,9 @@ export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({ messages, onText, case 'ollama': return sendOllamaMsg({ messages, onText, onFinalMessage, apiConfig }); default: - console.error(`Error: whichApi was ${whichApi}, which is not recognized!`); - return sendClaudeMsg({ messages, onText, onFinalMessage, apiConfig }); // TODO + console.error(`Error: whichApi was ${apiConfig.whichApi}, which is not recognized!`); + return { abort: () => { } } + //return sendClaudeMsg({ messages, onText, onFinalMessage, apiConfig }); // TODO } } diff --git a/extensions/void/src/extension.ts b/extensions/void/src/extension.ts index d95c2873..835dcade 100644 --- a/extensions/void/src/extension.ts +++ b/extensions/void/src/extension.ts @@ -33,6 +33,7 @@ const getApiConfig = () => { return apiConfig } + export function activate(context: vscode.ExtensionContext) { // 1. Mount the chat sidebar @@ -116,14 +117,16 @@ export function activate(context: vscode.ExtensionContext) { const oldContents = await readFileContentOfUri(editor.document.uri) const suggestedEdits = getDiffedLines(oldContents, m.code) await approvalCodeLensProvider.addNewApprovals(editor, suggestedEdits) - } - else if (m.type === 'getApiConfig') { + } else if (m.type === 'getApiConfig') { const apiConfig = getApiConfig() console.log('Api config:', apiConfig) webview.postMessage({ type: 'apiConfig', apiConfig } satisfies WebviewMessage) + } else if (m.type === 'displayError') { + + vscode.window.showWarningMessage(m.message, { modal: true }); } else { diff --git a/extensions/void/src/shared_types.ts b/extensions/void/src/shared_types.ts index 4d7c0dc9..722c3218 100644 --- a/extensions/void/src/shared_types.ts +++ b/extensions/void/src/shared_types.ts @@ -27,6 +27,9 @@ type WebviewMessage = ( // editor -> sidebar | { type: 'apiConfig', apiConfig: ApiConfig } + // Display native vscode error + | { type: 'displayError', message: string } // + ) type Command = WebviewMessage['type'] diff --git a/extensions/void/src/sidebar/Sidebar.tsx b/extensions/void/src/sidebar/Sidebar.tsx index 0107e228..61426869 100644 --- a/extensions/void/src/sidebar/Sidebar.tsx +++ b/extensions/void/src/sidebar/Sidebar.tsx @@ -166,6 +166,7 @@ const Sidebar = () => { // if user pressed ctrl+l, add their selection to the sidebar if (m.type === 'ctrl+l') { + setSelection(m.selection) const filepath = m.selection.filePath @@ -223,10 +224,12 @@ const Sidebar = () => { }, apiConfig: apiConfig }) - abortFnRef.current = abort + + abortFnRef.current = abort } + const onStop = useCallback(() => { // abort claude abortFnRef.current?.() @@ -266,7 +269,7 @@ const Sidebar = () => { {!selection?.selectionStr ? null : (
- +const FilesSelector = ({ + files, + setFiles, +}: { + files: vscode.Uri[]; + setFiles: (files: vscode.Uri[]) => void; +}) => { + return ( + files.length !== 0 && ( +
+ Include files: + {files.map((filename, i) => ( +
+ {/* X button on a file */} + +
+ ))}
- )} -
-} + ) + ); +}; const IncludedFiles = ({ files }: { files: vscode.Uri[] }) => { - return files.length !== 0 &&
- {files.map((filename, i) => -
- + return ( + files.length !== 0 && ( +
+ {files.map((filename, i) => ( +
+ +
+ ))}
- )} -
-} - + ) + ); +}; const ChatBubble = ({ chatMessage }: { chatMessage: ChatMessage }) => { + const role = chatMessage.role; + const children = chatMessage.displayContent; - const role = chatMessage.role - const children = chatMessage.displayContent + if (!children) return null; - if (!children) - return null + let chatbubbleContents: React.ReactNode; - let chatbubbleContents: React.ReactNode - - if (role === 'user') { - chatbubbleContents = <> - - {chatMessage.selection?.selectionStr && } - {children} - - } - else if (role === 'assistant') { + if (role === "user") { + chatbubbleContents = ( + <> + + {chatMessage.selection?.selectionStr && ( + + )} + {children} + + ); + } else if (role === "assistant") { const tokens = marked.lexer(children); // https://marked.js.org/using_pro#renderer - chatbubbleContents = // sectionsHTML + chatbubbleContents = ; // sectionsHTML } - - return
-
- {chatbubbleContents} + return ( +
+
+ {chatbubbleContents} +
-
-} + ); +}; const getBasename = (pathStr: string) => { // "unixify" path - pathStr = pathStr.replace(/[/\\]+/g, '/'); // replace any / or \ or \\ with / - const parts = pathStr.split('/') // split on / - return parts[parts.length - 1] -} - -type ChatMessage = { - role: 'user' - content: string, // content sent to the llm - displayContent: string, // content displayed to user - selection: Selection | null, // the user's selection - files: vscode.Uri[], // the files sent in the message -} | { - role: 'assistant', - content: string, // content received from LLM - displayContent: string // content displayed to user (this is the same as content for now) -} + pathStr = pathStr.replace(/[/\\]+/g, "/"); // replace any / or \ or \\ with / + const parts = pathStr.split("/"); // split on / + return parts[parts.length - 1]; +}; +type ChatMessage = + | { + role: "user"; + content: string; // content sent to the llm + displayContent: string; // content displayed to user + selection: Selection | null; // the user's selection + files: vscode.Uri[]; // the files sent in the message + } + | { + role: "assistant"; + content: string; // content received from LLM + displayContent: string; // content displayed to user (this is the same as content for now) + }; // const [stateRef, setState] = useInstantState(initVal) // setState instantly changes the value of stateRef instead of having to wait until the next render + const useInstantState = (initVal: T) => { - const stateRef = useRef(initVal) - const [_, setS] = useState(initVal) + const stateRef = useRef(initVal); + const [_, setS] = useState(initVal); const setState = useCallback((newVal: T) => { setS(newVal); stateRef.current = newVal; - }, []) - return [stateRef as React.RefObject, setState] as const // make s.current readonly - setState handles all changes -} - - + }, []); + return [stateRef as React.RefObject, setState] as const; // make s.current readonly - setState handles all changes +}; const Sidebar = () => { - // state of current message - const [selection, setSelection] = useState(null) // the code the user is selecting - const [files, setFiles] = useState([]) // the names of the files in the chat - const [instructions, setInstructions] = useState('') // the user's instructions + const [selection, setSelection] = useState(null); // the code the user is selecting + const [files, setFiles] = useState([]); // the names of the files in the chat + const [instructions, setInstructions] = useState(""); // the user's instructions // state of chat - const [chatMessageHistory, setChatHistory] = useState([]) - const [messageStream, setMessageStream] = useState('') - const [isLoading, setIsLoading] = useState(false) + const [chatMessageHistory, setChatHistory] = useState([]); + const [messageStream, setMessageStream] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const [isDisabled, setIsDisabled] = useState(false); + const [errorShown, setErrorShown] = useState(false); - const abortFnRef = useRef<(() => void) | null>(null) - const [apiConfig, setApiConfig] = useState(null) + const abortFnRef = useRef<(() => void) | null>(null); + + const [apiConfig, setApiConfig] = useState(null); + + const checkApiConfig = (apiConfig: ApiConfig) => { + if ( + (apiConfig.anthropic.apikey === "" && + apiConfig.greptile.apikey === "" && + apiConfig.openai.apikey === "" && + (apiConfig.ollama.endpoint === "" || + apiConfig.ollama.model === "")) || + apiConfig.whichApi === "" + ) { + setIsDisabled(true); + } else { + setIsDisabled(false); + } + + } // get Api Config on mount useEffect(() => { - getVSCodeAPI().postMessage({ type: 'getApiConfig' }) - }, []) + getVSCodeAPI().postMessage({ type: "getApiConfig" }); + }, []); // Receive messages from the extension useEffect(() => { const listener = (event: MessageEvent) => { - const m = event.data as WebviewMessage; // resolve any awaiting promises // eg. it will resolve the promise below for `await VSCodeResponse('files')` - resolveAwaitingVSCodeResponse(m) + resolveAwaitingVSCodeResponse(m); // if user pressed ctrl+l, add their selection to the sidebar - if (m.type === 'ctrl+l') { + if (m.type === "ctrl+l") { + if (isDisabled) { + getVSCodeAPI().postMessage({ + type: "displayError", + message: "Required API keys are not set.", + }); + return; + } + setSelection(m.selection); - - setSelection(m.selection) - - const filepath = m.selection.filePath + const filepath = m.selection.filePath; // add file if it's not a duplicate - if (!files.find(f => f.fsPath === filepath.fsPath)) setFiles(files => [...files, filepath]) - + if (!files.find((f) => f.fsPath === filepath.fsPath)) + setFiles((files) => [...files, filepath]); } // when get apiConfig, set - else if (m.type === 'apiConfig') { - setApiConfig(m.apiConfig) + else if (m.type === "apiConfig") { + setApiConfig(m.apiConfig); + checkApiConfig(m.apiConfig); } + }; + window.addEventListener("message", listener); + return () => { + window.removeEventListener("message", listener); + }; + }, [files, selection, isDisabled]); - } - window.addEventListener('message', listener); - return () => { window.removeEventListener('message', listener) } - }, [files, selection]) - - - const formRef = useRef(null) + const formRef = useRef(null); const onSubmit = async (e: FormEvent) => { + e.preventDefault(); + if (isLoading || isDisabled) return; - e.preventDefault() - if (isLoading) return - - setIsLoading(true) - setInstructions(''); + setIsLoading(true); + setInstructions(""); formRef.current?.reset(); // reset the form's text - setSelection(null) - setFiles([]) + setSelection(null); + setFiles([]); // request file content from vscode and await response - getVSCodeAPI().postMessage({ type: 'requestFiles', filepaths: files }) - const relevantFiles = await awaitVSCodeResponse('files') + getVSCodeAPI().postMessage({ type: "requestFiles", filepaths: files }); + const relevantFiles = await awaitVSCodeResponse("files"); // add message to chat history - const content = userInstructionsStr(instructions, relevantFiles.files, selection) + const content = userInstructionsStr( + instructions, + relevantFiles.files, + selection + ); // console.log('prompt:\n', content) - const newHistoryElt: ChatMessage = { role: 'user', content, displayContent: instructions, selection, files } - setChatHistory(chatMessageHistory => [...chatMessageHistory, newHistoryElt]) + const newHistoryElt: ChatMessage = { + role: "user", + content, + displayContent: instructions, + selection, + files, + }; + setChatHistory((chatMessageHistory) => [ + ...chatMessageHistory, + newHistoryElt, + ]); // send message to claude let { abort } = sendLLMMessage({ - messages: [...chatMessageHistory.map(m => ({ role: m.role, content: m.content })), { role: 'user', content }], + messages: [ + ...chatMessageHistory.map((m) => ({ + role: m.role, + content: m.content, + })), + { role: "user", content }, + ], onText: (newText, fullText) => setMessageStream(fullText), onFinalMessage: (content) => { - // add assistant's message to chat history - const newHistoryElt: ChatMessage = { role: 'assistant', content, displayContent: content, } - setChatHistory(chatMessageHistory => [...chatMessageHistory, newHistoryElt]) + const newHistoryElt: ChatMessage = { + role: "assistant", + content, + displayContent: content, + }; + setChatHistory((chatMessageHistory) => [ + ...chatMessageHistory, + newHistoryElt, + ]); // clear selection - setMessageStream('') - setIsLoading(false) + setMessageStream(""); + setIsLoading(false); }, - apiConfig: apiConfig - }) - - - abortFnRef.current = abort - } + apiConfig: apiConfig, + }); + abortFnRef.current = abort; + }; const onStop = useCallback(() => { // abort claude - abortFnRef.current?.() + abortFnRef.current?.(); // if messageStream was not empty, add it to the history - const llmContent = messageStream || '(canceled)' - const newHistoryElt: ChatMessage = { role: 'assistant', displayContent: messageStream, content: llmContent } - setChatHistory(chatMessageHistory => [...chatMessageHistory, newHistoryElt]) + const llmContent = messageStream || "(canceled)"; + const newHistoryElt: ChatMessage = { + role: "assistant", + displayContent: messageStream, + content: llmContent, + }; + setChatHistory((chatMessageHistory) => [ + ...chatMessageHistory, + newHistoryElt, + ]); - setMessageStream('') - setIsLoading(false) - - }, [messageStream]) + setMessageStream(""); + setIsLoading(false); + }, [messageStream]); //Clear code selection const clearSelection = () => { setSelection(null); }; - return <> -
-
- {/* previous messages */} - {chatMessageHistory.map((message, i) => - - )} - {/* message stream */} - -
- {/* chatbar */} -
- {/* selection */} -
- {/* selected files */} - - {/* selected code */} - {!selection?.selectionStr ? null - : ( + return ( + <> +
+
+ {/* previous messages */} + {chatMessageHistory.map((message, i) => ( + + ))} + {/* message stream */} + +
+ {/* chatbar */} +
+ {/* selection */} +
+ {/* selected files */} + + {/* selected code */} + {!selection?.selectionStr ? null : (
- +
- )} + )} +
+
{ + if (e.key === "Enter" && !e.shiftKey) onSubmit(e); + }} + onSubmit={(e) => { + console.log("submit!"); + e.preventDefault(); + onSubmit(e); + }} + > + {/* input */} + +