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
+ // })
+ // )
}