mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
vscode types in extension
This commit is contained in:
parent
fcc8de807f
commit
e4e1c7fb6c
8 changed files with 410 additions and 82 deletions
18
extensions/void/package-lock.json
generated
18
extensions/void/package-lock.json
generated
|
|
@ -23,7 +23,6 @@
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
"@types/react-syntax-highlighter": "^15.5.13",
|
"@types/react-syntax-highlighter": "^15.5.13",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"@types/vscode": "1.92.0",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^8.3.0",
|
"@typescript-eslint/eslint-plugin": "^8.3.0",
|
||||||
"@typescript-eslint/parser": "^8.3.0",
|
"@typescript-eslint/parser": "^8.3.0",
|
||||||
"@vscode/test-cli": "^0.0.10",
|
"@vscode/test-cli": "^0.0.10",
|
||||||
|
|
@ -53,7 +52,7 @@
|
||||||
"uuid": "^10.0.0"
|
"uuid": "^10.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "^1.92.0"
|
"vscode": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@alloc/quick-lru": {
|
"node_modules/@alloc/quick-lru": {
|
||||||
|
|
@ -1257,13 +1256,6 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/vscode": {
|
|
||||||
"version": "1.92.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.92.0.tgz",
|
|
||||||
"integrity": "sha512-DcZoCj17RXlzB4XJ7IfKdPTcTGDLYvTOcTNkvtjXWF+K2TlKzHHkBEXNWQRpBIXixNEUgx39cQeTFunY0E2msw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@types/yargs": {
|
"node_modules/@types/yargs": {
|
||||||
"version": "17.0.33",
|
"version": "17.0.33",
|
||||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
|
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
|
||||||
|
|
@ -6024,6 +6016,14 @@
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/monaco-editor": {
|
||||||
|
"version": "0.52.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.0.tgz",
|
||||||
|
"integrity": "sha512-OeWhNpABLCeTqubfqLMXGsqf6OmPU6pHM85kF3dhy6kq5hnhuVS1p3VrEW/XhWHc71P2tHyS5JFySD8mgs1crw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
"description": "",
|
"description": "",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "^1.92.0"
|
"vscode": "*"
|
||||||
},
|
},
|
||||||
"categories": [
|
"categories": [
|
||||||
"Other"
|
"Other"
|
||||||
|
|
@ -127,7 +127,6 @@
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
"@types/react-syntax-highlighter": "^15.5.13",
|
"@types/react-syntax-highlighter": "^15.5.13",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"@types/vscode": "1.92.0",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^8.3.0",
|
"@typescript-eslint/eslint-plugin": "^8.3.0",
|
||||||
"@typescript-eslint/parser": "^8.3.0",
|
"@typescript-eslint/parser": "^8.3.0",
|
||||||
"@vscode/test-cli": "^0.0.10",
|
"@vscode/test-cli": "^0.0.10",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import Anthropic from '@anthropic-ai/sdk';
|
import Anthropic from '@anthropic-ai/sdk';
|
||||||
import OpenAI from 'openai';
|
import OpenAI from 'openai';
|
||||||
import { Ollama } from 'ollama/browser'
|
import { Ollama } from 'ollama'
|
||||||
import { Content, GoogleGenerativeAI, GoogleGenerativeAIError, GoogleGenerativeAIFetchError } from '@google/generative-ai';
|
import { Content, GoogleGenerativeAI, GoogleGenerativeAIError, GoogleGenerativeAIFetchError } from '@google/generative-ai';
|
||||||
import { VoidConfig } from '../webviews/common/contextForConfig'
|
import { VoidConfig } from '../webviews/common/contextForConfig'
|
||||||
|
|
||||||
|
|
@ -11,36 +11,36 @@ export type OnText = (newText: string, fullText: string) => void
|
||||||
export type OnFinalMessage = (input: string) => void
|
export type OnFinalMessage = (input: string) => void
|
||||||
|
|
||||||
export type LLMMessageAnthropic = {
|
export type LLMMessageAnthropic = {
|
||||||
role: 'user' | 'assistant',
|
role: 'user' | 'assistant';
|
||||||
content: string,
|
content: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LLMMessage = {
|
export type LLMMessage = {
|
||||||
role: 'system' | 'user' | 'assistant',
|
role: 'system' | 'user' | 'assistant';
|
||||||
content: string,
|
content: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type SendLLMMessageFnTypeInternal = (params: {
|
type SendLLMMessageFnTypeInternal = (params: {
|
||||||
messages: LLMMessage[],
|
messages: LLMMessage[];
|
||||||
onText: OnText,
|
onText: OnText;
|
||||||
onFinalMessage: OnFinalMessage,
|
onFinalMessage: OnFinalMessage;
|
||||||
onError: (error: string) => void,
|
onError: (error: string) => void;
|
||||||
voidConfig: VoidConfig,
|
voidConfig: VoidConfig;
|
||||||
abortRef: AbortRef,
|
abortRef: AbortRef;
|
||||||
}) => void
|
}) => void
|
||||||
|
|
||||||
type SendLLMMessageFnTypeExternal = (params: {
|
type SendLLMMessageFnTypeExternal = (params: {
|
||||||
messages: LLMMessage[],
|
messages: LLMMessage[];
|
||||||
onText: OnText,
|
onText: OnText;
|
||||||
onFinalMessage: (fullText: string) => void,
|
onFinalMessage: (fullText: string) => void;
|
||||||
onError: (error: string) => void,
|
onError: (error: string) => void;
|
||||||
voidConfig: VoidConfig | null,
|
voidConfig: VoidConfig | null;
|
||||||
abortRef: AbortRef,
|
abortRef: AbortRef;
|
||||||
}) => void
|
}) => void
|
||||||
|
|
||||||
const parseMaxTokensStr = (maxTokensStr: string) => {
|
const parseMaxTokensStr = (maxTokensStr: string) => {
|
||||||
// parse the string but only if the full string is a valid number, eg parseInt('100abc') should return NaN
|
// parse the string but only if the full string is a valid number, eg parseInt('100abc') should return NaN
|
||||||
let int = isNaN(Number(maxTokensStr)) ? undefined : parseInt(maxTokensStr)
|
const int = isNaN(Number(maxTokensStr)) ? undefined : parseInt(maxTokensStr)
|
||||||
if (Number.isNaN(int))
|
if (Number.isNaN(int))
|
||||||
return undefined
|
return undefined
|
||||||
return int
|
return int
|
||||||
|
|
@ -79,7 +79,7 @@ const sendAnthropicMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFi
|
||||||
stream.on('finalMessage', (claude_response) => {
|
stream.on('finalMessage', (claude_response) => {
|
||||||
if (did_abort) return
|
if (did_abort) return
|
||||||
// stringify the response's content
|
// stringify the response's content
|
||||||
let content = claude_response.content.map(c => { if (c.type === 'text') { return c.text } }).join('\n');
|
const content = claude_response.content.map(c => c.type === 'text' ? c.text : c.type).join('\n');
|
||||||
onFinalMessage(content)
|
onFinalMessage(content)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -117,7 +117,7 @@ const sendGeminiMsg: SendLLMMessageFnTypeInternal = async ({ messages, onText, o
|
||||||
|
|
||||||
// remove system messages that get sent to Gemini
|
// remove system messages that get sent to Gemini
|
||||||
// str of all system messages
|
// str of all system messages
|
||||||
let systemMessage = messages
|
const systemMessage = messages
|
||||||
.filter(msg => msg.role === 'system')
|
.filter(msg => msg.role === 'system')
|
||||||
.map(msg => msg.content)
|
.map(msg => msg.content)
|
||||||
.join('\n');
|
.join('\n');
|
||||||
|
|
@ -173,7 +173,7 @@ const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinal
|
||||||
let openai: OpenAI
|
let openai: OpenAI
|
||||||
let options: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming
|
let options: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming
|
||||||
|
|
||||||
let maxTokens = parseMaxTokensStr(voidConfig.default.maxTokens)
|
const maxTokens = parseMaxTokensStr(voidConfig.default.maxTokens)
|
||||||
|
|
||||||
if (voidConfig.default.whichApi === 'openAI') {
|
if (voidConfig.default.whichApi === 'openAI') {
|
||||||
openai = new OpenAI({ apiKey: voidConfig.openAI.apikey, dangerouslyAllowBrowser: true });
|
openai = new OpenAI({ apiKey: voidConfig.openAI.apikey, dangerouslyAllowBrowser: true });
|
||||||
|
|
@ -181,10 +181,10 @@ const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinal
|
||||||
}
|
}
|
||||||
else if (voidConfig.default.whichApi === 'openRouter') {
|
else if (voidConfig.default.whichApi === 'openRouter') {
|
||||||
openai = new OpenAI({
|
openai = new OpenAI({
|
||||||
baseURL: "https://openrouter.ai/api/v1", apiKey: voidConfig.openRouter.apikey, dangerouslyAllowBrowser: true,
|
baseURL: 'https://openrouter.ai/api/v1', apiKey: voidConfig.openRouter.apikey, dangerouslyAllowBrowser: true,
|
||||||
defaultHeaders: {
|
defaultHeaders: {
|
||||||
"HTTP-Referer": 'https://voideditor.com', // Optional, for including your app on openrouter.ai rankings.
|
'HTTP-Referer': 'https://voideditor.com', // Optional, for including your app on openrouter.ai rankings.
|
||||||
"X-Title": 'Void Editor', // Optional. Shows in rankings on openrouter.ai.
|
'X-Title': 'Void Editor', // Optional. Shows in rankings on openrouter.ai.
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
options = { model: voidConfig.openRouter.model, messages: messages, stream: true, max_completion_tokens: maxTokens }
|
options = { model: voidConfig.openRouter.model, messages: messages, stream: true, max_completion_tokens: maxTokens }
|
||||||
|
|
@ -235,7 +235,7 @@ const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinal
|
||||||
export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, abortRef }) => {
|
export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, abortRef }) => {
|
||||||
|
|
||||||
let didAbort = false
|
let didAbort = false
|
||||||
let fullText = ""
|
let fullText = ''
|
||||||
|
|
||||||
// if abort is called, onFinalMessage is NOT called, and no later onTexts are called either
|
// if abort is called, onFinalMessage is NOT called, and no later onTexts are called either
|
||||||
abortRef.current = () => {
|
abortRef.current = () => {
|
||||||
|
|
@ -289,9 +289,9 @@ const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFin
|
||||||
fetch('https://api.greptile.com/v2/query', {
|
fetch('https://api.greptile.com/v2/query', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": `Bearer ${voidConfig.greptile.apikey}`,
|
'Authorization': `Bearer ${voidConfig.greptile.apikey}`,
|
||||||
"X-Github-Token": `${voidConfig.greptile.githubPAT}`,
|
'X-Github-Token': `${voidConfig.greptile.githubPAT}`,
|
||||||
"Content-Type": `application/json`,
|
'Content-Type': `application/json`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
messages,
|
messages,
|
||||||
|
|
@ -310,7 +310,7 @@ const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFin
|
||||||
if (didAbort)
|
if (didAbort)
|
||||||
return
|
return
|
||||||
|
|
||||||
for (let response of responseArr) {
|
for (const response of responseArr) {
|
||||||
|
|
||||||
const type: string = response['type']
|
const type: string = response['type']
|
||||||
const message = response['message']
|
const message = response['message']
|
||||||
|
|
@ -321,7 +321,7 @@ const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFin
|
||||||
onText(message, fullText)
|
onText(message, fullText)
|
||||||
}
|
}
|
||||||
else if (type === 'sources') {
|
else if (type === 'sources') {
|
||||||
const { filepath, linestart, lineend } = message as { filepath: string, linestart: number | null, lineend: number | null }
|
const { filepath, linestart: _, lineend: _2 } = message as { filepath: string; linestart: number | null; lineend: number | null }
|
||||||
fullText += filepath
|
fullText += filepath
|
||||||
onText(filepath, fullText)
|
onText(filepath, fullText)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import * as vscode from 'vscode';
|
import type * as vscode from 'vscode';
|
||||||
|
|
||||||
import { AbortRef, sendLLMMessage } from '../common/sendLLMMessage';
|
import { AbortRef, sendLLMMessage } from '../common/sendLLMMessage';
|
||||||
import { DiffArea } from '../common/shared_types';
|
import { DiffArea } from '../common/shared_types';
|
||||||
import { writeFileWithDiffInstructions, searchDiffChunkInstructions } from '../common/systemPrompts';
|
import { writeFileWithDiffInstructions, searchDiffChunkInstructions } from '../common/systemPrompts';
|
||||||
|
|
|
||||||
|
|
@ -10,28 +10,6 @@ import { readFileContentOfUri } from './extensionLib/readFileContentOfUri';
|
||||||
import { SidebarWebviewProvider } from './providers/SidebarWebviewProvider';
|
import { SidebarWebviewProvider } from './providers/SidebarWebviewProvider';
|
||||||
import { CtrlKWebviewProvider } from './providers/CtrlKWebviewProvider';
|
import { CtrlKWebviewProvider } from './providers/CtrlKWebviewProvider';
|
||||||
|
|
||||||
// this comes from vscode.proposed.editorInsets.d.ts
|
|
||||||
declare module 'vscode' {
|
|
||||||
export interface WebviewEditorInset {
|
|
||||||
readonly editor: vscode.TextEditor;
|
|
||||||
readonly line: number;
|
|
||||||
readonly height: number;
|
|
||||||
readonly webview: vscode.Webview;
|
|
||||||
readonly onDidDispose: Event<void>;
|
|
||||||
dispose(): void;
|
|
||||||
}
|
|
||||||
export namespace window {
|
|
||||||
export function createWebviewTextEditorInset(editor: vscode.TextEditor, line: number, height: number, options?: vscode.WebviewOptions): WebviewEditorInset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// this comes from vscode.d.ts
|
|
||||||
declare module 'vscode' {
|
|
||||||
export namespace languages {
|
|
||||||
export function addInlineDiff(editor: vscode.TextEditor, originalText: string, modifiedRange: Range): void;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const roundRangeToLines = (selection: vscode.Selection) => {
|
const roundRangeToLines = (selection: vscode.Selection) => {
|
||||||
let endLine = selection.end.character === 0 ? selection.end.line - 1 : selection.end.line // e.g. if the user triple clicks, it selects column=0, line=line -> column=0, line=line+1
|
let endLine = selection.end.character === 0 ? selection.end.line - 1 : selection.end.line // e.g. if the user triple clicks, it selects column=0, line=line -> column=0, line=line+1
|
||||||
return new vscode.Range(selection.start.line, 0, endLine, Number.MAX_SAFE_INTEGER)
|
return new vscode.Range(selection.start.line, 0, endLine, Number.MAX_SAFE_INTEGER)
|
||||||
|
|
|
||||||
|
|
@ -3,22 +3,6 @@
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { updateWebviewHTML as _updateWebviewHTML, updateWebviewHTML } from '../extensionLib/updateWebviewHTML';
|
import { updateWebviewHTML as _updateWebviewHTML, updateWebviewHTML } from '../extensionLib/updateWebviewHTML';
|
||||||
|
|
||||||
// this comes from vscode.proposed.editorInsets.d.ts
|
|
||||||
declare module 'vscode' {
|
|
||||||
export interface WebviewEditorInset {
|
|
||||||
readonly editor: vscode.TextEditor;
|
|
||||||
readonly line: number;
|
|
||||||
readonly height: number;
|
|
||||||
readonly webview: vscode.Webview;
|
|
||||||
readonly onDidDispose: Event<void>;
|
|
||||||
dispose(): void;
|
|
||||||
}
|
|
||||||
export namespace window {
|
|
||||||
export function createWebviewTextEditorInset(editor: vscode.TextEditor, line: number, height: number, options?: vscode.WebviewOptions): WebviewEditorInset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export class CtrlKWebviewProvider {
|
export class CtrlKWebviewProvider {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*"
|
"src/**/*",
|
||||||
|
"../../src/vscode-dts/vscode.d.ts",
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules"
|
"node_modules"
|
||||||
|
|
@ -27,4 +28,4 @@
|
||||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,365 @@
|
||||||
|
// import Anthropic from '@anthropic-ai/sdk';
|
||||||
|
// import OpenAI from 'openai';
|
||||||
|
// import { Ollama } from 'ollama'
|
||||||
|
// import { Content, GoogleGenerativeAI, GoogleGenerativeAIError, GoogleGenerativeAIFetchError } from '@google/generative-ai';
|
||||||
|
// 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;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// type SendLLMMessageFnTypeInternal = (params: {
|
||||||
|
// messages: LLMMessage[];
|
||||||
|
// onText: OnText;
|
||||||
|
// onFinalMessage: OnFinalMessage;
|
||||||
|
// onError: (error: string) => void;
|
||||||
|
// voidConfig: VoidConfig;
|
||||||
|
// abortRef: AbortRef;
|
||||||
|
// }) => void
|
||||||
|
|
||||||
|
// type SendLLMMessageFnTypeExternal = (params: {
|
||||||
|
// messages: LLMMessage[];
|
||||||
|
// onText: OnText;
|
||||||
|
// onFinalMessage: (fullText: string) => void;
|
||||||
|
// onError: (error: string) => void;
|
||||||
|
// voidConfig: VoidConfig | null;
|
||||||
|
// abortRef: AbortRef;
|
||||||
|
// }) => void
|
||||||
|
|
||||||
|
// const parseMaxTokensStr = (maxTokensStr: string) => {
|
||||||
|
// // parse the string but only if the full string is a valid number, eg parseInt('100abc') should return NaN
|
||||||
|
// const int = isNaN(Number(maxTokensStr)) ? undefined : parseInt(maxTokensStr)
|
||||||
|
// if (Number.isNaN(int))
|
||||||
|
// return undefined
|
||||||
|
// return int
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Anthropic
|
||||||
|
// const sendAnthropicMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig }) => {
|
||||||
|
|
||||||
|
// const anthropic = new Anthropic({ apiKey: voidConfig.anthropic.apikey, dangerouslyAllowBrowser: true }); // defaults to process.env["ANTHROPIC_API_KEY"]
|
||||||
|
|
||||||
|
// // find system messages and concatenate them
|
||||||
|
// const systemMessage = messages
|
||||||
|
// .filter(msg => msg.role === 'system')
|
||||||
|
// .map(msg => msg.content)
|
||||||
|
// .join('\n');
|
||||||
|
|
||||||
|
// // remove system messages for Anthropic
|
||||||
|
// const anthropicMessages = messages.filter(msg => msg.role !== 'system') as LLMMessageAnthropic[]
|
||||||
|
|
||||||
|
// const stream = anthropic.messages.stream({
|
||||||
|
// system: systemMessage,
|
||||||
|
// messages: anthropicMessages,
|
||||||
|
// model: voidConfig.anthropic.model,
|
||||||
|
// max_tokens: parseMaxTokensStr(voidConfig.default.maxTokens)!, // this might be undefined, but it will just throw an error for the user
|
||||||
|
// });
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// const content = claude_response.content.map(c => c.type === 'text' ? c.text : c.type).join('\n');
|
||||||
|
// onFinalMessage(content)
|
||||||
|
// })
|
||||||
|
|
||||||
|
// stream.on('error', (error) => {
|
||||||
|
// // the most common error will be invalid API key (401), so we handle this with a nice message
|
||||||
|
// if (error instanceof Anthropic.APIError && error.status === 401) {
|
||||||
|
// onError('Invalid API key.')
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// onError(error.message)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
|
||||||
|
// // if abort is called, onFinalMessage is NOT called, and no later onTexts are called either
|
||||||
|
// const abort = () => {
|
||||||
|
// did_abort = true
|
||||||
|
// stream.controller.abort() // TODO need to test this to make sure it works, it might throw an error
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return { abort }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// // Gemini
|
||||||
|
// const sendGeminiMsg: SendLLMMessageFnTypeInternal = async ({ messages, onText, onFinalMessage, onError, voidConfig, abortRef }) => {
|
||||||
|
|
||||||
|
// let didAbort = false
|
||||||
|
// let fullText = ''
|
||||||
|
|
||||||
|
// abortRef.current = () => {
|
||||||
|
// didAbort = true
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const genAI = new GoogleGenerativeAI(voidConfig.gemini.apikey);
|
||||||
|
// const model = genAI.getGenerativeModel({ model: voidConfig.gemini.model });
|
||||||
|
|
||||||
|
// // remove system messages that get sent to Gemini
|
||||||
|
// // str of all system messages
|
||||||
|
// const systemMessage = messages
|
||||||
|
// .filter(msg => msg.role === 'system')
|
||||||
|
// .map(msg => msg.content)
|
||||||
|
// .join('\n');
|
||||||
|
|
||||||
|
// // Convert messages to Gemini format
|
||||||
|
// const geminiMessages: Content[] = messages
|
||||||
|
// .filter(msg => msg.role !== 'system')
|
||||||
|
// .map((msg, i) => ({
|
||||||
|
// parts: [{ text: msg.content }],
|
||||||
|
// role: msg.role === 'assistant' ? 'model' : 'user'
|
||||||
|
// }))
|
||||||
|
|
||||||
|
// model.generateContentStream({ contents: geminiMessages, systemInstruction: systemMessage, })
|
||||||
|
// .then(async response => {
|
||||||
|
// abortRef.current = () => {
|
||||||
|
// // response.stream.return(fullText)
|
||||||
|
// 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 {
|
||||||
|
// onError(error);
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // OpenAI, OpenRouter, OpenAICompatible
|
||||||
|
// const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, abortRef }) => {
|
||||||
|
|
||||||
|
// let didAbort = false
|
||||||
|
// let fullText = ''
|
||||||
|
|
||||||
|
// // if abort is called, onFinalMessage is NOT called, and no later onTexts are called either
|
||||||
|
// abortRef.current = () => {
|
||||||
|
// didAbort = true;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// let openai: OpenAI
|
||||||
|
// let options: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming
|
||||||
|
|
||||||
|
// const maxTokens = parseMaxTokensStr(voidConfig.default.maxTokens)
|
||||||
|
|
||||||
|
// if (voidConfig.default.whichApi === 'openAI') {
|
||||||
|
// openai = new OpenAI({ apiKey: voidConfig.openAI.apikey, dangerouslyAllowBrowser: true });
|
||||||
|
// options = { model: voidConfig.openAI.model, messages: messages, stream: true, max_completion_tokens: maxTokens }
|
||||||
|
// }
|
||||||
|
// else if (voidConfig.default.whichApi === 'openRouter') {
|
||||||
|
// openai = new OpenAI({
|
||||||
|
// baseURL: 'https://openrouter.ai/api/v1', apiKey: voidConfig.openRouter.apikey, dangerouslyAllowBrowser: true,
|
||||||
|
// defaultHeaders: {
|
||||||
|
// 'HTTP-Referer': 'https://voideditor.com', // Optional, for including your app on openrouter.ai rankings.
|
||||||
|
// 'X-Title': 'Void Editor', // Optional. Shows in rankings on openrouter.ai.
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// options = { model: voidConfig.openRouter.model, messages: messages, stream: true, max_completion_tokens: maxTokens }
|
||||||
|
// }
|
||||||
|
// else if (voidConfig.default.whichApi === 'openAICompatible') {
|
||||||
|
// openai = new OpenAI({ baseURL: voidConfig.openAICompatible.endpoint, apiKey: voidConfig.openAICompatible.apikey, dangerouslyAllowBrowser: true })
|
||||||
|
// options = { model: voidConfig.openAICompatible.model, messages: messages, stream: true, max_completion_tokens: maxTokens }
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// console.error(`sendOpenAIMsg: invalid whichApi: ${voidConfig.default.whichApi}`)
|
||||||
|
// throw new Error(`voidConfig.whichAPI was invalid: ${voidConfig.default.whichApi}`)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// openai.chat.completions
|
||||||
|
// .create(options)
|
||||||
|
// .then(async response => {
|
||||||
|
// abortRef.current = () => {
|
||||||
|
// // response.controller.abort()
|
||||||
|
// didAbort = true;
|
||||||
|
// }
|
||||||
|
// // when receive text
|
||||||
|
// for await (const chunk of response) {
|
||||||
|
// if (didAbort) return;
|
||||||
|
// const newText = chunk.choices[0]?.delta?.content || '';
|
||||||
|
// fullText += newText;
|
||||||
|
// onText(newText, fullText);
|
||||||
|
// }
|
||||||
|
// onFinalMessage(fullText);
|
||||||
|
// })
|
||||||
|
// // when error/fail - this catches errors of both .create() and .then(for await)
|
||||||
|
// .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);
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
|
||||||
|
// };
|
||||||
|
|
||||||
|
// // Ollama
|
||||||
|
// export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, abortRef }) => {
|
||||||
|
|
||||||
|
// let didAbort = false
|
||||||
|
// let fullText = ''
|
||||||
|
|
||||||
|
// // if abort is called, onFinalMessage is NOT called, and no later onTexts are called either
|
||||||
|
// abortRef.current = () => {
|
||||||
|
// didAbort = true;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const ollama = new Ollama({ host: voidConfig.ollama.endpoint })
|
||||||
|
|
||||||
|
// ollama.chat({
|
||||||
|
// model: voidConfig.ollama.model,
|
||||||
|
// messages: messages,
|
||||||
|
// stream: true,
|
||||||
|
// options: { num_predict: parseMaxTokensStr(voidConfig.default.maxTokens) } // this is max_tokens
|
||||||
|
// })
|
||||||
|
// .then(async stream => {
|
||||||
|
// abortRef.current = () => {
|
||||||
|
// // stream.abort()
|
||||||
|
// didAbort = true
|
||||||
|
// }
|
||||||
|
// // iterate through the stream
|
||||||
|
// for await (const chunk of stream) {
|
||||||
|
// if (didAbort) return;
|
||||||
|
// const newText = chunk.message.content;
|
||||||
|
// fullText += newText;
|
||||||
|
// onText(newText, fullText);
|
||||||
|
// }
|
||||||
|
// onFinalMessage(fullText);
|
||||||
|
|
||||||
|
// })
|
||||||
|
// // when error/fail
|
||||||
|
// .catch(error => {
|
||||||
|
// onError(error)
|
||||||
|
// })
|
||||||
|
|
||||||
|
// };
|
||||||
|
|
||||||
|
// // Greptile
|
||||||
|
// // https://docs.greptile.com/api-reference/query
|
||||||
|
// // https://docs.greptile.com/quickstart#sample-response-streamed
|
||||||
|
|
||||||
|
// const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, abortRef }) => {
|
||||||
|
|
||||||
|
// let didAbort = false
|
||||||
|
// let fullText = ''
|
||||||
|
|
||||||
|
// // if abort is called, onFinalMessage is NOT called, and no later onTexts are called either
|
||||||
|
// abortRef.current = () => {
|
||||||
|
// didAbort = true
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 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],
|
||||||
|
// }),
|
||||||
|
// })
|
||||||
|
// // 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 (didAbort)
|
||||||
|
// return
|
||||||
|
|
||||||
|
// for (const response of responseArr) {
|
||||||
|
|
||||||
|
// 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: _2 } = 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 => {
|
||||||
|
// onError(e)
|
||||||
|
// });
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({ messages, onText, onFinalMessage, onError, voidConfig, abortRef }) => {
|
||||||
|
// if (!voidConfig) return;
|
||||||
|
|
||||||
|
// // trim message content (Anthropic and other providers give an error if there is trailing whitespace)
|
||||||
|
// messages = messages.map(m => ({ ...m, content: m.content.trim() }))
|
||||||
|
|
||||||
|
// 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!`)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
Loading…
Reference in a new issue