Compare commits

..

5 commits

Author SHA1 Message Date
igardev
79b90ef999
v0.0.47 2026-05-04 08:42:41 +03:00
igardev
a544d93511
API key for getting models list, ultiline field for Edit with AI, qwen3.5 models added
- API key is used (if needed and provided) on getting the list of models for adding OpenAI compatible provider
- Multiline field for Edit with AI
- Qwen3.5 models (2B, 4B, 9B) added in the predefined list  - good for tools and chat
2026-05-04 08:42:01 +03:00
Alexey Mekhanoshin
a73d9498ab
feat: add authorization headers to models fetch request (#180)
Adds support for the Authorization header when fetching the list of models from an OpenAI-compatible provider.
2026-04-30 07:59:35 +03:00
igardev
29f6c9973b
v0.0.46 2026-04-29 21:06:11 +03:00
Copilot
f98919badf
Add llama.vscode model provider for GitHub Copilot Chat (#171)
With this change llama.vscode could provide models for VS Code Copilot:
1. Start tools model from llama-vscode (local or external)  
2. In VS Code Copilot show the models list -> Other Models -> Manage Models  
3. Make the models (all models available by the application serving the tools model are shown) you want to use visible (click on the left of the model name)  
4. Select the desired model from Copilot and start using it

Not needed tools from Copilot could be unchecked to reduce contex size if local model is used.
2026-04-29 21:04:33 +03:00
4 changed files with 255 additions and 25 deletions

View file

@ -2,7 +2,7 @@
"name": "llama-vscode",
"displayName": "llama-vscode",
"description": "Local LLM-assisted text completion using llama.cpp",
"version": "0.0.45",
"version": "0.0.47",
"publisher": "ggml-org",
"repository": "https://github.com/ggml-org/llama.vscode",
"engines": {

View file

@ -79,7 +79,36 @@ export const PREDEFINED_LISTS = new Map<string, any>([
"endpoint": "http://127.0.0.1:8010"
}
]],
[PREDEFINED_LISTS_KEYS.TOOLS, [
[PREDEFINED_LISTS_KEYS.TOOLS,
[
{
"name": "Qwen3.5-2B-GGUF:Q8_0 (LOCAL) (CPU)",
"localStartCommand": "llama-server -hf unsloth/Qwen3.5-2B-GGUF:Q8_0 --jinja -c 0 -ub 1024 -b 1024 --cache-reuse 256 --port 8009 --host 127.0.0.1",
"endpoint": "http://localhost:8009",
"aiModel": "",
"isKeyRequired": false
},
{
"name": "Qwen3.5-2B-GGUF:Q8_0 (LOCAL) (VRAM>3GB)",
"localStartCommand": "llama-server -hf unsloth/Qwen3.5-2B-GGUF:Q8_0 --jinja -ngl 99 -c 0 -ub 1024 -b 1024 --cache-reuse 256 --port 8009 --host 127.0.0.1",
"endpoint": "http://localhost:8009",
"aiModel": "",
"isKeyRequired": false
},
{
"name": "Qwen3.5-4B-GGUF:Q8_0 (LOCAL) (VRAM>6GB)",
"localStartCommand": "llama-server -hf unsloth/Qwen3.5-4B-GGUF:Q8_0 --jinja -c 0 -ub 1024 -b 1024 --cache-reuse 256 --port 8009 --host 127.0.0.1",
"endpoint": "http://localhost:8009",
"aiModel": "",
"isKeyRequired": false
},
{
"name": "Qwen3.5-9B-GGUF:Q8_0 (LOCAL) (VRAM>12GB)",
"localStartCommand": "llama-server -hf unsloth/Qwen3.5-9B-GGUF:Q8_0 --jinja -c 0 -ub 1024 -b 1024 --cache-reuse 256 --port 8009 --host 127.0.0.1",
"endpoint": "http://localhost:8009",
"aiModel": "",
"isKeyRequired": false
},
{
"name": "OpenAI gpt-oss 20B (LOCAL) (> 19GB VRAM)",
"localStartCommand": "llama-server -hf ggml-org/gpt-oss-20b-GGUF -c 0 --jinja --reasoning-format none -np 2 --port 8009",

View file

@ -44,13 +44,13 @@ export class OpenAiCompModelStrategy implements IAddStrategy {
prompt: 'example: http://localhost:8080 or https://openrauter.ai/api'
})??""
isKeyRequired = await Utils.confirmAction(`Is API key required for this endpoint (${endpoint})?`, "");
}
}
if (!endpoint){
vscode.window.showWarningMessage("Endpoint is not provided!")
return;
}
const providerModels: QuickPickItem[] = [];
const models = await this.getModels(endpoint);
const models = await this.getModels(endpoint, isKeyRequired);
if (models.length == 0) {
vscode.window.showInformationMessage("No models are found.")
return
@ -108,30 +108,50 @@ export class OpenAiCompModelStrategy implements IAddStrategy {
}
}
private async getModels(endpoint: string): Promise<OpenAiCompModel[]> {
let hfEndpoint = Utils.trimTrailingSlash(endpoint) +"/v1/models";
private async getModels(endpoint: string, isKeyRequired: boolean): Promise<OpenAiCompModel[]> {
const hfEndpoint = Utils.trimTrailingSlash(endpoint) + "/v1/models";
// Create a request configuration
let requestConfig: any = {};
if (isKeyRequired) {
// We get the saved key for this specific endpoint
const apiKey = this.app.persistence.getApiKey(endpoint);
if (apiKey) {
requestConfig = {
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
}
};
}
}
try {
let result = await axios.default.get(
`${Utils.trimTrailingSlash(hfEndpoint)}`
const result = await axios.default.get(
`${Utils.trimTrailingSlash(hfEndpoint)}`,
requestConfig
);
let models: OpenAiCompModel[] = [];
let modelsList: OpenAiCompModel[] = []
if (result && result.data && result.data.models) modelsList = result.data.models
else if (result && result.data && result.data.data) modelsList = result.data.data
if (modelsList.length > 0){
for(let mdl of modelsList){
models.push(mdl)
let modelsList: OpenAiCompModel[] = [];
if (result && result.data && result.data.models) modelsList = result.data.models;
else if (result && result.data && result.data.data) modelsList = result.data.data;
if (modelsList.length > 0) {
for (let mdl of modelsList) {
models.push(mdl);
}
}
return models;
} catch (error){
vscode.window.showErrorMessage("Error getting provider models): " + error)
} catch (error) {
vscode.window.showErrorMessage("Error getting provider models: " + error);
return [];
}
}
private sanitizeInput(input: string): string {
return input ? input.trim() : '';
}

View file

@ -26,6 +26,192 @@ export class TextEditor {
vscode.commands.executeCommand('setContext', 'textEditSuggestionVisible', visible);
}
private escapeWebviewAttr(value: string): string {
return value
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;');
}
/**
* Multiline instructions (webview); resolves undefined if cancelled or closed.
*/
private showMultilineEditPrompt(): Promise<string | undefined> {
const title =
this.app.configuration.getUiText('How would you like to modify the selected text?') ??
'How would you like to modify the selected text?';
const placeholder =
this.app.configuration.getUiText('Enter your instructions for editing the text...') ??
'Enter your instructions for editing the text...';
const submitLabel = this.app.configuration.getUiText('Submit') ?? 'Submit';
const cancelLabel = this.app.configuration.getUiText('Cancel') ?? 'Cancel';
const emptyHint =
this.app.configuration.getUiText('Please enter editing instructions.') ??
'Please enter editing instructions.';
return new Promise((resolve) => {
let settled = false;
const panel = vscode.window.createWebviewPanel(
'editWithAiMultilinePrompt',
title,
{ viewColumn: vscode.ViewColumn.Beside, preserveFocus: false },
{ enableScripts: true }
);
const finish = (value: string | undefined) => {
if (settled) {
return;
}
settled = true;
resolve(value);
panel.dispose();
};
const cspSource = panel.webview.cspSource;
panel.webview.html = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${cspSource} 'unsafe-inline'; script-src 'unsafe-inline' ${cspSource};">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
box-sizing: border-box;
margin: 0;
padding: 12px;
height: 100vh;
display: flex;
flex-direction: column;
font-family: var(--vscode-font-family);
font-size: var(--vscode-font-size);
color: var(--vscode-foreground);
background-color: var(--vscode-editor-background);
}
label {
margin-bottom: 8px;
}
textarea {
flex: 1;
min-height: 120px;
resize: vertical;
padding: 8px;
border: 1px solid var(--vscode-input-border);
background: var(--vscode-input-background);
color: var(--vscode-input-foreground);
font-family: var(--vscode-editor-font-family);
font-size: var(--vscode-editor-font-size);
}
textarea:focus {
outline: 1px solid var(--vscode-focusBorder);
}
.actions {
margin-top: 12px;
display: flex;
gap: 8px;
justify-content: flex-end;
}
/* DOM order is Submit then Cancel (Tab: textarea → Submit → Cancel); flex order keeps Cancel left, Submit right. */
.actions .secondary {
order: 1;
}
.actions .primary {
order: 2;
}
button {
padding: 6px 14px;
border: none;
cursor: pointer;
font-size: var(--vscode-font-size);
}
.primary {
background: var(--vscode-button-background);
color: var(--vscode-button-foreground);
}
.primary:hover {
background: var(--vscode-button-hoverBackground);
}
.secondary {
background: var(--vscode-button-secondaryBackground);
color: var(--vscode-button-secondaryForeground);
}
.secondary:hover {
background: var(--vscode-button-secondaryHoverBackground);
}
</style>
</head>
<body>
<label for="prompt">${this.escapeWebviewAttr(title)}</label>
<textarea id="prompt" placeholder="${this.escapeWebviewAttr(placeholder)}" autofocus></textarea>
<div class="actions">
<button type="button" class="primary" id="submit">${this.escapeWebviewAttr(submitLabel)}</button>
<button type="button" class="secondary" id="cancel">${this.escapeWebviewAttr(cancelLabel)}</button>
</div>
<script>
const vscode = acquireVsCodeApi();
const ta = document.getElementById('prompt');
function focusPrompt() {
if (!ta) {
return;
}
ta.focus();
const len = ta.value.length;
ta.setSelectionRange(len, len);
}
window.addEventListener('load', focusPrompt);
requestAnimationFrame(focusPrompt);
setTimeout(focusPrompt, 0);
setTimeout(focusPrompt, 100);
window.addEventListener('message', (event) => {
const data = event.data;
if (data && data.command === 'focusPrompt') {
focusPrompt();
}
});
document.getElementById('submit').addEventListener('click', () => {
vscode.postMessage({ command: 'submit', text: ta.value });
});
document.getElementById('cancel').addEventListener('click', () => {
vscode.postMessage({ command: 'cancel' });
});
</script>
</body>
</html>`;
const requestPromptFocus = () => {
void panel.webview.postMessage({ command: 'focusPrompt' });
};
panel.onDidChangeViewState((e) => {
if (e.webviewPanel.visible) {
requestPromptFocus();
}
});
requestPromptFocus();
setTimeout(requestPromptFocus, 50);
setTimeout(requestPromptFocus, 200);
panel.webview.onDidReceiveMessage((message) => {
if (message.command === 'submit') {
const text = typeof message.text === 'string' ? message.text : '';
if (!text.trim()) {
void vscode.window.showInformationMessage(emptyHint);
return;
}
finish(text);
} else if (message.command === 'cancel') {
finish(undefined);
}
});
panel.onDidDispose(() => {
if (!settled) {
settled = true;
resolve(undefined);
}
});
});
}
async showEditPrompt(editor: vscode.TextEditor) {
let chatUrl = this.app.configuration.endpoint_chat
if (!chatUrl) chatUrl = this.app.configuration.endpoint_tools;
@ -64,12 +250,7 @@ export class TextEditor {
const contextRange = new vscode.Range(startLine, 0, endLine, editor.document.lineAt(endLine).text.length);
const context = editor.document.getText(contextRange);
// Create and show input box
const prompt = await vscode.window.showInputBox({
placeHolder: 'Enter your instructions for editing the text...',
prompt: 'How would you like to modify the selected text?',
ignoreFocusOut: true
});
const prompt = await this.showMultilineEditPrompt();
if (!prompt) {
return;