import * as vscode from "vscode"; import axios from "axios"; let VSConfig: vscode.WorkspaceConfiguration; let apiEndpoint: string; let apiAuthentication: string; let apiModel: string; let apiTemperature: number; let numPredict: number; let promptWindowSize: number; let completionKeys: string; let responsePreview: boolean | undefined; let responsePreviewMaxTokens: number; let responsePreviewDelay: number; let continueInline: boolean | undefined; let keepAlive: number | undefined; let topP: number | undefined; function updateVSConfig() { VSConfig = vscode.workspace.getConfiguration("fabelous-autocoder"); apiEndpoint = VSConfig.get("endpoint") || "http://localhost:11434/api/generate"; apiAuthentication = VSConfig.get("authentication") || ""; apiModel = VSConfig.get("model") || "fabelous-coder:latest"; numPredict = VSConfig.get("max tokens predicted") || 1000; promptWindowSize = VSConfig.get("prompt window size") || 2000; completionKeys = VSConfig.get("completion keys") || " "; responsePreview = VSConfig.get("response preview"); responsePreviewMaxTokens = VSConfig.get("preview max tokens") || 50; responsePreviewDelay = VSConfig.get("preview delay") || 0; continueInline = VSConfig.get("continue inline"); apiTemperature = VSConfig.get("temperature") || 0.7; keepAlive = VSConfig.get("keep alive") || 30; topP = VSConfig.get("top p") || 1; } updateVSConfig(); vscode.workspace.onDidChangeConfiguration(updateVSConfig); function getContextLines(document: vscode.TextDocument, position: vscode.Position): string { const lines = []; const startLine = Math.max(0, position.line - 1); const endLine = position.line; for (let i = startLine; i <= endLine; i++) { lines.push(document.lineAt(i).text); } return lines.join("\n"); } function createFIMPrompt(prefix: string, language: string): string { return `${prefix}${language}\n`; } async function autocompleteCommand(textEditor: vscode.TextEditor, cancellationToken?: vscode.CancellationToken) { const document = textEditor.document; const position = textEditor.selection.active; const context = getContextLines(document, position); const fimPrompt = createFIMPrompt(context, document.languageId); vscode.window.withProgress( { location: vscode.ProgressLocation.Notification, title: "Fabelous Autocoder", cancellable: true, }, async (progress, progressCancellationToken) => { try { progress.report({ message: "Starting model..." }); let axiosCancelPost: () => void; const axiosCancelToken = new axios.CancelToken((c) => { axiosCancelPost = () => { c("Autocompletion request terminated by user cancel"); }; if (cancellationToken) cancellationToken.onCancellationRequested(axiosCancelPost); progressCancellationToken.onCancellationRequested(axiosCancelPost); vscode.workspace.onDidCloseTextDocument(axiosCancelPost); }); const response = await axios.post(apiEndpoint, { model: apiModel, prompt: fimPrompt, stream: false, raw: true, options: { num_predict: numPredict, temperature: apiTemperature, stop: ["", "```"] } }, { cancelToken: axiosCancelToken, headers: { 'Authorization': apiAuthentication } }); progress.report({ message: "Generating..." }); let completionText = response.data.response; // Remove any FIM tags and leading/trailing whitespace completionText = completionText.replace(/||/g, '').trim(); // Remove the context lines const startLine = Math.max(0, position.line - 1); const endLine = position.line; const rangeToReplace = new vscode.Range( new vscode.Position(startLine, 0), new vscode.Position(endLine, document.lineAt(endLine).text.length) ); // Apply the edit const edit = new vscode.WorkspaceEdit(); edit.replace(document.uri, rangeToReplace, completionText); await vscode.workspace.applyEdit(edit); // Move the cursor to the end of the inserted text const newPosition = new vscode.Position(startLine + completionText.split('\n').length - 1, completionText.split('\n').pop()!.length); textEditor.selection = new vscode.Selection(newPosition, newPosition); progress.report({ message: "Fabelous completion finished." }); } catch (err: any) { vscode.window.showErrorMessage( "Fabelous Autocoder encountered an error: " + err.message ); console.log(err); } } ); } async function provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, cancellationToken: vscode.CancellationToken) { const item = new vscode.CompletionItem("Fabelous autocompletion"); item.insertText = new vscode.SnippetString('${1:}'); if (responsePreview) { await new Promise(resolve => setTimeout(resolve, responsePreviewDelay * 1000)); if (cancellationToken.isCancellationRequested) { return [ item ]; } const context = getContextLines(document, position); const fimPrompt = createFIMPrompt(context, document.languageId); try { const response_preview = await axios.post(apiEndpoint, { model: apiModel, prompt: fimPrompt, stream: false, raw: true, options: { num_predict: responsePreviewMaxTokens, temperature: apiTemperature, stop: ['', '\n', '```'], ...(keepAlive && { keep_alive: keepAlive }), ...(topP && { top_p: topP }), } }, { cancelToken: new axios.CancelToken((c) => { cancellationToken.onCancellationRequested(() => c("Autocompletion request terminated by completion cancel")); }) }); } catch (error) { console.error("Error fetching preview:", error); } } item.documentation = new vscode.MarkdownString('Press `Enter` to get an autocompletion from Fabelous Autocoder'); if (continueInline || !responsePreview) { item.command = { command: 'fabelous-autocoder.autocomplete', title: 'Fabelous Autocomplete', arguments: [cancellationToken] }; } return [item]; } function activate(context: vscode.ExtensionContext) { const completionProvider = vscode.languages.registerCompletionItemProvider("*", { provideCompletionItems }, ...completionKeys.split("") ); const externalAutocompleteCommand = vscode.commands.registerTextEditorCommand( "fabelous-autocoder.autocomplete", (textEditor, _, cancellationToken?) => { autocompleteCommand(textEditor, cancellationToken); } ); context.subscriptions.push(completionProvider); context.subscriptions.push(externalAutocompleteCommand); } function deactivate() { } module.exports = { activate, deactivate, };