diff --git a/.vscode/launch.json b/.vscode/launch.json index 1210201..84eb950 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,12 +6,16 @@ "version": "0.2.0", "configurations": [ { - "name": "Run Extension", + "name": "Watch & Launch Extension", "type": "extensionHost", "request": "launch", "args": ["--extensionDevelopmentPath=${workspaceFolder}"], - "outFiles": ["${workspaceFolder}/out/**/*.js"], - "preLaunchTask": "${defaultBuildTask}" + "outFiles": ["${workspaceFolder}/dist/*.js"], + "preLaunchTask": "npm: watch", + "debugWebviews": true, + "runtimeExecutable": "${execPath}", + "smartStep": true, + "sourceMaps": true }, { "name": "Extension Tests", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 0a101bd..5cf8c24 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -16,6 +16,15 @@ "kind": "build", "isDefault": true } + }, + { + "type": "npm", + "script": "watch", + "group": { + "kind": "build", + "isDefault": true + }, + "isBackground": true } ] } diff --git a/src/common/completion/cloudCompletion.ts b/src/common/completion/cloudCompletion.ts new file mode 100644 index 0000000..f83741a --- /dev/null +++ b/src/common/completion/cloudCompletion.ts @@ -0,0 +1,39 @@ +import { OpenAI } from "@langchain/openai"; +import { configuration } from "../utils/configuration"; +import Logger from "../logger"; + +type Parameters = { + temperature: number; + n_predict: number; + controller?: AbortController; +}; + +export const sendCompletionsRequestCloud = async ( + prompt: string, + parameters: Parameters +) => { + const apiKey = configuration.get("cloud.apiToken"); + + const model = new OpenAI({ + maxRetries: 0, + openAIApiKey: apiKey, + configuration: { + baseURL: configuration.get("cloud.endpoint"), + }, + stop: ["<|file_separator|>"], + temperature: parameters.temperature, + maxTokens: parameters.n_predict, + }); + try { + const response = await model.invoke(prompt, { + configurable: { + signal: parameters.controller, + }, + maxConcurrency: 1, + }); + + return response; + } catch (error) { + Logger.error(error); + } +}; diff --git a/src/common/completion/index.ts b/src/common/completion/index.ts index 806bd26..b72c5a0 100644 --- a/src/common/completion/index.ts +++ b/src/common/completion/index.ts @@ -4,10 +4,12 @@ import { getPromptCompletion } from "../prompt"; import { abortInterval, delay } from "./utils/intervals"; import { getAdditionalDocuments } from "./utils/getAdditionalDocuments"; import Logger from "../logger"; -import { sendCompletionRequest } from "./localCompletion"; +import { sendCompletionRequestLocal } from "./localCompletion"; import { servers } from "../server"; import { configuration } from "../utils/configuration"; import { state } from "../utils/state"; +import { sendCompletionsRequestCloud } from "./cloudCompletion"; +import statusBar from "../statusBar"; const logCompletion = () => { const uuid = randomUUID(); @@ -39,10 +41,12 @@ export const getInlineCompletionProvider = ( "inlineSuggestModeAuto" ); + // If the current mode is not auto and the trigger is automatic, don't suggest any completions. if (currentInlineSuggestModeAuto !== true && triggerAuto === true) { return []; } + // If there is a selected completion item, don't suggest any completions. if (context.selectedCompletionInfo) { return []; } @@ -52,42 +56,14 @@ export const getInlineCompletionProvider = ( loggerCompletion.info("Completion: started"); const cancelled = await delay(250, token); + if (cancelled) { loggerCompletion.info("Completion: canceled"); return []; } const { abortController, requestFinish } = abortInterval(token); - - const modelType = triggerAuto - ? configuration.get("completion.autoMode") - : configuration.get("completion.manuallyMode"); - - const serverUrl = servers[modelType].serverUrl; - - const additionalDocuments: vscode.TextDocument[] = configuration.get( - "experimental.useopentabs" - ) - ? await getAdditionalDocuments() - : []; - - const prompt = await getPromptCompletion({ - activeDocument: document, - additionalDocuments: additionalDocuments, - position: position, - maxTokenExpect: triggerAuto ? maxToken : 2000, - }); - - const parameters = triggerAuto - ? { - n_predict: 128, - stop: ["\n", "<|file_separator|>"], - } - : { - n_predict: 512, - stop: ["<|file_separator|>"], - temperature: 0.5, - }; + const { stopTask } = statusBar.startTask(); try { Logger.info( @@ -97,40 +73,95 @@ export const getInlineCompletionProvider = ( sendTelemetry: true, } ); + if (configuration.get("cloud.use")) { + const prompt = await getPromptCompletion({ + activeDocument: document, + additionalDocuments: await getAdditionalDocuments(), + position: position, + maxTokenExpect: 3300, + }); + const completion = await sendCompletionsRequestCloud(prompt, { + n_predict: 512, + temperature: 0.5, + controller: abortController, + }); - const completion = await sendCompletionRequest( - prompt, - parameters, - abortController, - loggerCompletion.uuid(), - serverUrl - ); + if (completion === "" || completion === undefined) { + return []; + } - if (completion === null) { - return []; - } + if (token.isCancellationRequested) { + loggerCompletion.info( + "Request: canceled by new completion cancel token" + ); - if (token.isCancellationRequested) { - loggerCompletion.info( - "Request: canceled by new completion cancel token" - ); + return []; + } - return []; - } - if (triggerAuto) { - maxToken *= expectedTime / completion.timing; - } + return [ + { + insertText: completion, + range: new vscode.Range(position, position), + }, + ]; + } else { + const additionalDocuments: vscode.TextDocument[] = configuration.get( + "experimental.useopentabs" + ) + ? await getAdditionalDocuments() + : []; + + const prompt = await getPromptCompletion({ + activeDocument: document, + additionalDocuments: additionalDocuments, + position: position, + maxTokenExpect: triggerAuto ? maxToken : 2000, + }); + + const parameters = triggerAuto + ? { + n_predict: 128, + stop: ["\n", "<|file_separator|>"], + } + : { + n_predict: 512, + stop: ["<|file_separator|>"], + temperature: 0.5, + }; + const modelType = triggerAuto + ? configuration.get("completion.autoMode") + : configuration.get("completion.manuallyMode"); + const completion = await sendCompletionRequestLocal( + prompt, + parameters, + abortController, + loggerCompletion.uuid(), + servers[modelType].serverUrl + ); - loggerCompletion.info(`maxToken: ${maxToken}`); + if (completion === null) { + return []; + } - loggerCompletion.info("Request: finished"); + if (token.isCancellationRequested) { + loggerCompletion.info( + "Request: canceled by new completion cancel token" + ); - return [ - { - insertText: completion.content, - range: new vscode.Range(position, position), - }, - ]; + return []; + } + if (triggerAuto) { + maxToken *= expectedTime / completion.timing; + } + loggerCompletion.info("Request: finished"); + + return [ + { + insertText: completion.content, + range: new vscode.Range(position, position), + }, + ]; + } } catch (error) { const Error = error as Error; Logger.error(error); @@ -138,8 +169,10 @@ export const getInlineCompletionProvider = ( const errorMessage = Error.message; vscode.window.showErrorMessage(errorMessage); } finally { + stopTask(); requestFinish(); } + return []; }, }; diff --git a/src/common/completion/localCompletion.ts b/src/common/completion/localCompletion.ts index 6b8cc8b..42f1976 100644 --- a/src/common/completion/localCompletion.ts +++ b/src/common/completion/localCompletion.ts @@ -1,7 +1,6 @@ import * as vscode from "vscode"; import { randomUUID } from "crypto"; import Logger from "../logger"; -import statusBar from "../statusBar"; const logCompletion = (uuid = randomUUID() as string) => { return { @@ -36,15 +35,13 @@ const defualtParameters = { slot_id: -1, }; -export const sendCompletionRequest = async ( +export const sendCompletionRequestLocal = async ( prompt: string, parameters: Record, abortController: AbortController, uuid: string, url: string ) => { - const { stopTask } = statusBar.startTask(); - const loggerCompletion = logCompletion(uuid); const parametersForCompletion = { @@ -120,7 +117,5 @@ export const sendCompletionRequest = async ( const errorMessage = Error.message; vscode.window.showErrorMessage(errorMessage); return null; - } finally { - stopTask(); } }; diff --git a/src/common/prompt/promptCompletion.ts b/src/common/prompt/promptCompletion.ts index fa4ffd6..7a6c86c 100644 --- a/src/common/prompt/promptCompletion.ts +++ b/src/common/prompt/promptCompletion.ts @@ -25,10 +25,10 @@ const tokenize = async (text: string): Promise => { const getTextNormalized = (text: string) => { return text - .replace("<|fim_prefix|>", "") - .replace("<|fim_middle|>", "") - .replace("<|fim_suffix|>", "") - .replace("<|file_separator|>", ""); + .replaceAll("<|fim_prefix|>", "") + .replaceAll("<|fim_middle|>", "") + .replaceAll("<|fim_suffix|>", "") + .replaceAll("<|file_separator|>", ""); }; const spliteDocumentByPosition = ( @@ -161,7 +161,7 @@ export const getPromptCompletion = async ({ }) => { const start = performance.now(); - const maxTokenHardLimit = 6000; + const maxTokenHardLimit = 4000; const maxToken = maxTokenExpect > maxTokenHardLimit ? maxTokenHardLimit : maxTokenExpect; @@ -209,7 +209,7 @@ export const getPromptCompletion = async ({ const activeDocumentFileName = additionalDocumentsText === "" - ? "" + ? "<|fim_prefix|>" : "\n" + "https://github.com/" + getRelativePath(activeDocument.uri) + diff --git a/src/extension.ts b/src/extension.ts index 0c0eea3..b132532 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -76,15 +76,19 @@ export async function activate(context: vscode.ExtensionContext) { event.affectsConfiguration("firecoder.server.usePreRelease") ) { Object.values(servers).forEach((server) => server.stopServer()); + + const completionServers = configuration.get("cloud.use") + ? [] + : new Set([ + configuration.get("completion.autoMode"), + configuration.get("completion.manuallyMode"), + ]); const serversToStart = [ - ...new Set([ - configuration.get("completion.autoMode"), - configuration.get("completion.manuallyMode"), - ...(configuration.get("experimental.chat") && - !configuration.get("cloud.use") - ? ["chat-medium" as const] - : []), - ]), + ...completionServers, + ...(configuration.get("experimental.chat") && + !configuration.get("cloud.use") + ? ["chat-medium" as const] + : []), ]; await Promise.all( serversToStart.map((serverType) => servers[serverType].startServer()) @@ -94,43 +98,52 @@ export async function activate(context: vscode.ExtensionContext) { (async () => { if (configuration.get("cloud.use")) { - Logger.info("Use cloud for chat", { + Logger.info("Use cloud for chat and completions", { component: "main", sendTelemetry: true, }); - } - try { - const serversStarted = await Promise.all( - [ - ...new Set([ - configuration.get("completion.autoMode"), - configuration.get("completion.manuallyMode"), + const InlineCompletionProvider = getInlineCompletionProvider(context); + vscode.languages.registerInlineCompletionItemProvider( + { pattern: "**" }, + InlineCompletionProvider + ); + } else { + try { + const completionServers = configuration.get("cloud.use") + ? [] + : new Set([ + configuration.get("completion.autoMode"), + configuration.get("completion.manuallyMode"), + ]); + const serversStarted = await Promise.all( + [ + ...completionServers, ...(configuration.get("experimental.chat") && !configuration.get("cloud.use") ? ["chat-medium" as const] : []), - ]), - ].map((serverType) => servers[serverType].startServer()) - ); + ].map((serverType) => servers[serverType].startServer()) + ); - if (serversStarted.some((serverStarted) => serverStarted)) { - Logger.info("Server inited", { - component: "main", + if (serversStarted.some((serverStarted) => serverStarted)) { + Logger.info("Server inited", { + component: "main", + sendTelemetry: true, + }); + const InlineCompletionProvider = getInlineCompletionProvider(context); + vscode.languages.registerInlineCompletionItemProvider( + { pattern: "**" }, + InlineCompletionProvider + ); + } + } catch (error) { + vscode.window.showErrorMessage((error as Error).message); + Logger.error(error, { + component: "server", sendTelemetry: true, }); - const InlineCompletionProvider = getInlineCompletionProvider(context); - vscode.languages.registerInlineCompletionItemProvider( - { pattern: "**" }, - InlineCompletionProvider - ); } - } catch (error) { - vscode.window.showErrorMessage((error as Error).message); - Logger.error(error, { - component: "server", - sendTelemetry: true, - }); } Logger.info("FireCoder is ready.", {