mirror of
https://github.com/voideditor/void
synced 2026-05-23 17:38:23 +00:00
remove extensions/ code
This commit is contained in:
parent
02781927e1
commit
f2b176dfda
10 changed files with 0 additions and 529 deletions
|
|
@ -1,49 +0,0 @@
|
||||||
// Import message sending functions for different LLM providers
|
|
||||||
import { sendAnthropicMsg } from './providers/anthropic'
|
|
||||||
import { sendGeminiMsg } from './providers/gemini'
|
|
||||||
import { sendOpenAIMsg } from './providers/openai'
|
|
||||||
import { sendOllamaMsg } from './providers/ollama'
|
|
||||||
import { sendGreptileMsg } from './providers/greptile'
|
|
||||||
import { LLMMessage, OnText, OnFinalMessage, AbortRef } from './types'
|
|
||||||
import { VoidConfig } from '../../webviews/common/contextForConfig'
|
|
||||||
|
|
||||||
// Main function to send messages to LLM providers
|
|
||||||
export const sendLLMMessage = ({
|
|
||||||
messages,
|
|
||||||
onText,
|
|
||||||
onFinalMessage,
|
|
||||||
onError,
|
|
||||||
voidConfig,
|
|
||||||
abortRef
|
|
||||||
}: {
|
|
||||||
messages: LLMMessage[], // Array of messages to send
|
|
||||||
onText: OnText, // Callback for receiving text chunks
|
|
||||||
onFinalMessage: (fullText: string) => void, // Callback for final message
|
|
||||||
onError: (error: string) => void, // Error handling callback
|
|
||||||
voidConfig: VoidConfig | null, // Configuration object
|
|
||||||
abortRef: AbortRef, // Reference for aborting requests
|
|
||||||
}) => {
|
|
||||||
// Return early if no config is provided
|
|
||||||
if (!voidConfig) return
|
|
||||||
|
|
||||||
// Trim whitespace from all message contents
|
|
||||||
messages = messages.map(m => ({ ...m, content: m.content.trim() }))
|
|
||||||
|
|
||||||
// Route message to appropriate provider based on configuration
|
|
||||||
switch (voidConfig.default.whichApi) {
|
|
||||||
case 'anthropic':
|
|
||||||
return sendAnthropicMsg({ messages, onText, onFinalMessage, onError, voidConfig, abortRef })
|
|
||||||
case 'openAI':
|
|
||||||
case 'openRouter':
|
|
||||||
case 'openAICompatible':
|
|
||||||
return sendOpenAIMsg({ messages, onText, onFinalMessage, onError, voidConfig, abortRef })
|
|
||||||
case 'gemini':
|
|
||||||
return sendGeminiMsg({ messages, onText, onFinalMessage, onError, voidConfig, abortRef })
|
|
||||||
case 'ollama':
|
|
||||||
return sendOllamaMsg({ messages, onText, onFinalMessage, onError, voidConfig, abortRef })
|
|
||||||
case 'greptile':
|
|
||||||
return sendGreptileMsg({ messages, onText, onFinalMessage, onError, voidConfig, abortRef })
|
|
||||||
default:
|
|
||||||
onError(`Error: whichApi was ${voidConfig.default.whichApi}, which is not recognized!`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
import Anthropic from '@anthropic-ai/sdk'
|
|
||||||
import { SendLLMMessageParams, LLMMessageAnthropic } from '../types'
|
|
||||||
import { parseMaxTokensStr } from '../utils'
|
|
||||||
|
|
||||||
export const sendAnthropicMsg = ({
|
|
||||||
messages,
|
|
||||||
onText,
|
|
||||||
onFinalMessage,
|
|
||||||
onError,
|
|
||||||
voidConfig
|
|
||||||
}: SendLLMMessageParams) => {
|
|
||||||
const anthropic = new Anthropic({
|
|
||||||
apiKey: voidConfig.anthropic.apikey,
|
|
||||||
dangerouslyAllowBrowser: true
|
|
||||||
})
|
|
||||||
|
|
||||||
// Combine system messages into a single string
|
|
||||||
const systemMessage = messages
|
|
||||||
.filter(msg => msg.role === 'system')
|
|
||||||
.map(msg => msg.content)
|
|
||||||
.join('\n')
|
|
||||||
|
|
||||||
// Remove system messages and cast to Anthropic message type
|
|
||||||
const anthropicMessages = messages
|
|
||||||
.filter(msg => msg.role !== 'system') as LLMMessageAnthropic[]
|
|
||||||
|
|
||||||
let did_abort = false
|
|
||||||
|
|
||||||
const stream = anthropic.messages.stream({
|
|
||||||
system: systemMessage,
|
|
||||||
messages: anthropicMessages,
|
|
||||||
model: voidConfig.anthropic.model,
|
|
||||||
max_tokens: parseMaxTokensStr(voidConfig.default.maxTokens)!,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Handle streaming response
|
|
||||||
stream.on('text', (newText, fullText) => {
|
|
||||||
if (did_abort) return
|
|
||||||
onText(newText, fullText)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Handle final message
|
|
||||||
stream.on('finalMessage', (response) => {
|
|
||||||
if (did_abort) return
|
|
||||||
const content = response.content
|
|
||||||
.map(c => c.type === 'text' ? c.text : '')
|
|
||||||
.join('\n')
|
|
||||||
onFinalMessage(content)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Handle errors
|
|
||||||
stream.on('error', (error) => {
|
|
||||||
if (error instanceof Anthropic.APIError && error.status === 401) {
|
|
||||||
onError('Invalid API key.')
|
|
||||||
} else {
|
|
||||||
onError(error.message)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
abort: () => {
|
|
||||||
did_abort = true
|
|
||||||
stream.controller.abort()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
import { GoogleGenerativeAI, GoogleGenerativeAIFetchError } from '@google/generative-ai'
|
|
||||||
import { SendLLMMessageParams } from '../types'
|
|
||||||
import { parseMaxTokensStr } from '../utils'
|
|
||||||
|
|
||||||
export const sendGeminiMsg = async ({
|
|
||||||
messages,
|
|
||||||
onText,
|
|
||||||
onFinalMessage,
|
|
||||||
onError,
|
|
||||||
voidConfig,
|
|
||||||
abortRef
|
|
||||||
}: SendLLMMessageParams) => {
|
|
||||||
let didAbort = false
|
|
||||||
let fullText = ''
|
|
||||||
|
|
||||||
abortRef.current = () => {
|
|
||||||
didAbort = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const genAI = new GoogleGenerativeAI(voidConfig.gemini.apikey)
|
|
||||||
const model = genAI.getGenerativeModel({ model: voidConfig.gemini.model })
|
|
||||||
|
|
||||||
// Get system messages and combine them
|
|
||||||
const systemMessage = messages
|
|
||||||
.filter(msg => msg.role === 'system')
|
|
||||||
.map(msg => msg.content)
|
|
||||||
.join('\n')
|
|
||||||
|
|
||||||
// Convert messages to Gemini format
|
|
||||||
const geminiMessages = messages
|
|
||||||
.filter(msg => msg.role !== 'system')
|
|
||||||
.map(msg => ({
|
|
||||||
parts: [{ text: msg.content }],
|
|
||||||
role: msg.role === 'assistant' ? 'model' : 'user'
|
|
||||||
}))
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await model.generateContentStream({
|
|
||||||
contents: geminiMessages,
|
|
||||||
systemInstruction: systemMessage,
|
|
||||||
})
|
|
||||||
|
|
||||||
abortRef.current = () => {
|
|
||||||
didAbort = true
|
|
||||||
}
|
|
||||||
|
|
||||||
for await (const chunk of response.stream) {
|
|
||||||
if (didAbort) return
|
|
||||||
const newText = chunk.text()
|
|
||||||
fullText += newText
|
|
||||||
onText(newText, fullText)
|
|
||||||
}
|
|
||||||
|
|
||||||
onFinalMessage(fullText)
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof GoogleGenerativeAIFetchError) {
|
|
||||||
if (error.status === 400) {
|
|
||||||
onError('Invalid API key.')
|
|
||||||
} else {
|
|
||||||
onError(`${error.name}:\n${error.message}`)
|
|
||||||
}
|
|
||||||
} else if (error instanceof Error) {
|
|
||||||
onError(error.toString())
|
|
||||||
} else {
|
|
||||||
onError('Unknown error occurred')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
import { SendLLMMessageParams } from '../types'
|
|
||||||
|
|
||||||
// Response type for Greptile API
|
|
||||||
type GreptileResponse = {
|
|
||||||
type: 'message' | 'sources' | 'status'
|
|
||||||
message: string | {
|
|
||||||
filepath: string
|
|
||||||
linestart: number | null
|
|
||||||
lineend: number | null
|
|
||||||
} | ''
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sends a message to Greptile API and handles the streaming response
|
|
||||||
export const sendGreptileMsg = ({
|
|
||||||
messages,
|
|
||||||
onText,
|
|
||||||
onFinalMessage,
|
|
||||||
onError,
|
|
||||||
voidConfig,
|
|
||||||
abortRef
|
|
||||||
}: SendLLMMessageParams) => {
|
|
||||||
let didAbort = false
|
|
||||||
let fullText = ''
|
|
||||||
|
|
||||||
// Set up abort handler
|
|
||||||
abortRef.current = () => {
|
|
||||||
didAbort = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make API request to Greptile
|
|
||||||
fetch('https://api.greptile.com/v2/query', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
"Authorization": `Bearer ${voidConfig.greptile.apikey}`,
|
|
||||||
"X-Github-Token": `${voidConfig.greptile.githubPAT}`,
|
|
||||||
"Content-Type": `application/json`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
messages,
|
|
||||||
stream: true,
|
|
||||||
repositories: [voidConfig.greptile.repoinfo],
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
.then(async response => {
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}`)
|
|
||||||
}
|
|
||||||
// Parse the streaming response into JSON array
|
|
||||||
const text = await response.text()
|
|
||||||
return JSON.parse(`[${text.trim().split('\n').join(',')}]`) as GreptileResponse[]
|
|
||||||
})
|
|
||||||
.then(async responseArr => {
|
|
||||||
if (didAbort) return
|
|
||||||
|
|
||||||
// Process each response chunk
|
|
||||||
for (const response of responseArr) {
|
|
||||||
if (didAbort) break
|
|
||||||
|
|
||||||
switch (response.type) {
|
|
||||||
case 'message':
|
|
||||||
// Handle message chunks
|
|
||||||
fullText += response.message as string
|
|
||||||
onText(response.message as string, fullText)
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'sources': {
|
|
||||||
// Handle source reference chunks
|
|
||||||
const sourceInfo = response.message as {
|
|
||||||
filepath: string
|
|
||||||
linestart: number | null
|
|
||||||
lineend: number | null
|
|
||||||
}
|
|
||||||
const sourceText = `\nSource: ${sourceInfo.filepath}${sourceInfo.linestart
|
|
||||||
? ` (lines ${sourceInfo.linestart}-${sourceInfo.lineend})`
|
|
||||||
: ''
|
|
||||||
}\n`
|
|
||||||
fullText += sourceText
|
|
||||||
onText(sourceText, fullText)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'status':
|
|
||||||
// Handle completion status
|
|
||||||
if (!response.message) {
|
|
||||||
onFinalMessage(fullText)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
// Handle any errors that occur during the request
|
|
||||||
const errorMessage = error instanceof Error
|
|
||||||
? error.message
|
|
||||||
: 'An unknown error occurred'
|
|
||||||
onError(errorMessage)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,113 +0,0 @@
|
||||||
import { Ollama } from 'ollama/browser'
|
|
||||||
import { SendLLMMessageParams } from '../types'
|
|
||||||
import { parseMaxTokensStr } from '../utils'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if an Ollama model is installed
|
|
||||||
*/
|
|
||||||
async function checkModelExists(ollama: Ollama, modelName: string): Promise<{
|
|
||||||
exists: boolean,
|
|
||||||
installedModels: string[]
|
|
||||||
}> {
|
|
||||||
const models = await ollama.list()
|
|
||||||
const installedModels = models.models.map(m => m.name.replace(/:latest$/, ''))
|
|
||||||
const exists = installedModels.some(m => m.startsWith(modelName))
|
|
||||||
return { exists, installedModels }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build error message for when model is not found
|
|
||||||
*/
|
|
||||||
function buildModelNotFoundError(modelName: string, installedModels: string[]): string {
|
|
||||||
return [
|
|
||||||
`The model "${modelName}" is not available locally.`,
|
|
||||||
`Please run 'ollama pull ${modelName}' to download it first`,
|
|
||||||
`or try selecting one from the installed models:`,
|
|
||||||
installedModels.join(', ')
|
|
||||||
].join(' ')
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implementation of Ollama chat functionality
|
|
||||||
*/
|
|
||||||
export const sendOllamaMsg = async ({
|
|
||||||
messages,
|
|
||||||
onText,
|
|
||||||
onFinalMessage,
|
|
||||||
onError,
|
|
||||||
voidConfig,
|
|
||||||
abortRef
|
|
||||||
}: SendLLMMessageParams) => {
|
|
||||||
let didAbort = false
|
|
||||||
let fullText = ""
|
|
||||||
|
|
||||||
// Set up abort handler
|
|
||||||
abortRef.current = () => {
|
|
||||||
didAbort = true
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Initialize Ollama client
|
|
||||||
const ollama = new Ollama({
|
|
||||||
host: voidConfig.ollama.endpoint
|
|
||||||
})
|
|
||||||
|
|
||||||
// Check if model exists
|
|
||||||
const { exists, installedModels } = await checkModelExists(
|
|
||||||
ollama,
|
|
||||||
voidConfig.ollama.model
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!exists) {
|
|
||||||
const errorMessage = buildModelNotFoundError(
|
|
||||||
voidConfig.ollama.model,
|
|
||||||
installedModels
|
|
||||||
)
|
|
||||||
onText(errorMessage, errorMessage)
|
|
||||||
onFinalMessage(errorMessage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start streaming chat response
|
|
||||||
const stream = await ollama.chat({
|
|
||||||
model: voidConfig.ollama.model,
|
|
||||||
messages,
|
|
||||||
stream: true,
|
|
||||||
options: {
|
|
||||||
num_predict: parseMaxTokensStr(voidConfig.default.maxTokens)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Update abort handler
|
|
||||||
abortRef.current = () => {
|
|
||||||
didAbort = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle streaming response
|
|
||||||
for await (const chunk of stream) {
|
|
||||||
if (didAbort) return
|
|
||||||
|
|
||||||
const newText = chunk.message.content
|
|
||||||
fullText += newText
|
|
||||||
onText(newText, fullText)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send final message
|
|
||||||
onFinalMessage(fullText)
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
// Handle connection errors
|
|
||||||
if (error instanceof Error && error.message.includes('Failed to fetch')) {
|
|
||||||
const errorMessage = [
|
|
||||||
'Ollama service is not running.',
|
|
||||||
'Please start the Ollama service and try again.'
|
|
||||||
].join(' ')
|
|
||||||
onText(errorMessage, errorMessage)
|
|
||||||
onFinalMessage(errorMessage)
|
|
||||||
}
|
|
||||||
// Handle other errors
|
|
||||||
else if (error) {
|
|
||||||
onError(error.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,103 +0,0 @@
|
||||||
import OpenAI from 'openai'
|
|
||||||
import { SendLLMMessageParams } from '../types'
|
|
||||||
import { parseMaxTokensStr } from '../utils'
|
|
||||||
|
|
||||||
export const sendOpenAIMsg = ({
|
|
||||||
messages,
|
|
||||||
onText,
|
|
||||||
onFinalMessage,
|
|
||||||
onError,
|
|
||||||
voidConfig,
|
|
||||||
abortRef
|
|
||||||
}: SendLLMMessageParams) => {
|
|
||||||
let didAbort = false
|
|
||||||
let fullText = ''
|
|
||||||
|
|
||||||
abortRef.current = () => {
|
|
||||||
didAbort = true
|
|
||||||
}
|
|
||||||
|
|
||||||
let openai: OpenAI
|
|
||||||
let options: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming
|
|
||||||
|
|
||||||
const maxTokens = parseMaxTokensStr(voidConfig.default.maxTokens)
|
|
||||||
|
|
||||||
// Configure OpenAI client based on API type
|
|
||||||
switch (voidConfig.default.whichApi) {
|
|
||||||
case 'openAI':
|
|
||||||
openai = new OpenAI({
|
|
||||||
apiKey: voidConfig.openAI.apikey,
|
|
||||||
dangerouslyAllowBrowser: true
|
|
||||||
})
|
|
||||||
options = {
|
|
||||||
model: voidConfig.openAI.model,
|
|
||||||
messages,
|
|
||||||
stream: true,
|
|
||||||
max_tokens: maxTokens
|
|
||||||
}
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'openRouter':
|
|
||||||
openai = new OpenAI({
|
|
||||||
baseURL: "https://openrouter.ai/api/v1",
|
|
||||||
apiKey: voidConfig.openRouter.apikey,
|
|
||||||
dangerouslyAllowBrowser: true,
|
|
||||||
defaultHeaders: {
|
|
||||||
"HTTP-Referer": 'https://voideditor.com',
|
|
||||||
"X-Title": 'Void Editor',
|
|
||||||
}
|
|
||||||
})
|
|
||||||
options = {
|
|
||||||
model: voidConfig.openRouter.model,
|
|
||||||
messages,
|
|
||||||
stream: true,
|
|
||||||
max_tokens: maxTokens
|
|
||||||
}
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'openAICompatible':
|
|
||||||
openai = new OpenAI({
|
|
||||||
baseURL: voidConfig.openAICompatible.endpoint,
|
|
||||||
apiKey: voidConfig.openAICompatible.apikey,
|
|
||||||
dangerouslyAllowBrowser: true
|
|
||||||
})
|
|
||||||
options = {
|
|
||||||
model: voidConfig.openAICompatible.model,
|
|
||||||
messages,
|
|
||||||
stream: true,
|
|
||||||
max_tokens: maxTokens
|
|
||||||
}
|
|
||||||
break
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error(`Invalid whichApi: ${voidConfig.default.whichApi}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
openai.chat.completions
|
|
||||||
.create(options)
|
|
||||||
.then(async response => {
|
|
||||||
abortRef.current = () => {
|
|
||||||
didAbort = true
|
|
||||||
}
|
|
||||||
|
|
||||||
for await (const chunk of response) {
|
|
||||||
if (didAbort) return
|
|
||||||
const newText = chunk.choices[0]?.delta?.content || ''
|
|
||||||
fullText += newText
|
|
||||||
onText(newText, fullText)
|
|
||||||
}
|
|
||||||
|
|
||||||
onFinalMessage(fullText)
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
if (error instanceof OpenAI.APIError) {
|
|
||||||
if (error.status === 401) {
|
|
||||||
onError('Invalid API key.')
|
|
||||||
} else {
|
|
||||||
onError(`${error.name}:\n${error.message}`)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
onError(error)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
import { VoidConfig } from '../../webviews/common/contextForConfig'
|
|
||||||
|
|
||||||
export type AbortRef = { current: (() => void) | null }
|
|
||||||
|
|
||||||
export type OnText = (newText: string, fullText: string) => void
|
|
||||||
|
|
||||||
export type OnFinalMessage = (input: string) => void
|
|
||||||
|
|
||||||
export type LLMMessageAnthropic = {
|
|
||||||
role: 'user' | 'assistant',
|
|
||||||
content: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
export type LLMMessage = {
|
|
||||||
role: 'system' | 'user' | 'assistant',
|
|
||||||
content: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SendLLMMessageParams = {
|
|
||||||
messages: LLMMessage[],
|
|
||||||
onText: OnText,
|
|
||||||
onFinalMessage: OnFinalMessage,
|
|
||||||
onError: (error: string) => void,
|
|
||||||
voidConfig: VoidConfig,
|
|
||||||
abortRef: AbortRef,
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
export const parseMaxTokensStr = (maxTokensStr: string) => {
|
|
||||||
let int = isNaN(Number(maxTokensStr)) ? undefined : parseInt(maxTokensStr)
|
|
||||||
if (Number.isNaN(int))
|
|
||||||
return undefined
|
|
||||||
return int
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue