mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
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
This commit is contained in:
parent
78e10a5593
commit
7b31f2841a
4 changed files with 553 additions and 271 deletions
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 = `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
|
|
|
|||
|
|
@ -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<string, string>,
|
||||
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!`)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
// })
|
||||
// )
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue