Compare commits

...

2 commits

Author SHA1 Message Date
igardev
8f994f8bb3 - Refactor menu.ts model - extract services
- agent "Ask" added for questions about the project without changing the files
- predefiled free models from OpenRouter added (and xAi removed as not free anymore)
- Some bugs fixed
2025-10-05 15:42:42 +03:00
igardev
5ffd38efa8 menu.ts is refactored - services classes are extracted 2025-10-04 13:38:13 +03:00
34 changed files with 3241 additions and 1278 deletions

15
.vscode/launch.json vendored
View file

@ -18,6 +18,21 @@
// "preLaunchTask": "${defaultBuildTask}",
"preLaunchTask": "npm: watch",
"sourceMaps": true
},
{
"name": "Debug Tests",
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionTestsPath=${workspaceFolder}/dist/test/suite"
],
"outFiles": [
"${workspaceFolder}/dist/**/*.js"
],
"preLaunchTask": "npm: compile",
"sourceMaps": true,
"console": "integratedTerminal"
}
]
}

View file

@ -7,5 +7,9 @@
"out": true // set this to false to include "out" folder in search results
},
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
"typescript.tsc.autoDetect": "off"
"typescript.tsc.autoDetect": "off",
"testing.automaticallyOpenPeekView": "onFailure",
"testing.automaticallyOpenTestResults": "onFailure",
"mochaExplorer.files": "dist/test/suite/**/*.test.js",
"mochaExplorer.require": "dist/test/runTest.js"
}

2069
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -353,16 +353,16 @@
]
},
"default": [
{
{
"name": "llama-vscode help",
"description": "This is an agent for helping how to use llama-vscode.",
"systemInstruction": [
"You are an agent for helping the user how to use llama-vscode.",
"Use the available tools to get the help documentation for llama-vscode and answer the questions from the user.",
"Base your answers on the help documentation from the tools."
"You are an agent for helping the user how to use llama-vscode.",
"Use the available tools to get the help documentation for llama-vscode and answer the questions from the user.",
"Base your answers on the help documentation from the tools."
],
"tools": [
"llama_vscode_help"
"llama_vscode_help"
]
},
{
@ -480,15 +480,14 @@
]
},
"default": [
{
{
"name": "about",
"description": "Reviews the project and provides information about it.",
"prompt": [
"What is this project about?",
"Provide an overview of the project - purpose, architecture, language, etc."
"What is this project about?",
"Provide an overview of the project - purpose, architecture, language, etc."
],
"context": [
]
"context": []
},
{
"name": "explain",
@ -496,8 +495,7 @@
"prompt": [
"Explain the provided source code."
],
"context": [
]
"context": []
}
],
"description": "The list of agent commands, which could be selected by the user"
@ -987,7 +985,7 @@
"ragEnabled": {
"type": "boolean",
"default": true
},
},
"envStartLastUsed": {
"type": "boolean",
"default": false,
@ -1663,7 +1661,9 @@
"type": {
"type": "string",
"description": "Type of the tool",
"enum": ["function"]
"enum": [
"function"
]
},
"function": {
"type": "object",
@ -1684,7 +1684,9 @@
"type": {
"type": "string",
"description": "Type of parameters object",
"enum": ["object"]
"enum": [
"object"
]
},
"properties": {
"type": "object",
@ -1699,22 +1701,31 @@
}
}
},
"required": ["type", "properties"]
"required": [
"type",
"properties"
]
},
"strict": {
"type": "boolean",
"description": "Whether to use strict validation"
}
},
"required": ["name", "description", "parameters"]
"required": [
"name",
"description",
"parameters"
]
}
},
"required": ["type", "function"]
"required": [
"type",
"function"
]
}
}
},
"default": [
]
"default": []
},
"llama-vscode.context_custom": {
"type": "object",
@ -1928,9 +1939,14 @@
"watch": "tsc -watch -p ./",
"build-ui": "cd ui && npm install && npm run build",
"dev-ui": "cd ui && npm install && npm run dev",
"postinstall": "npm run build-ui"
"postinstall": "npm run build-ui",
"test": "node ./dist/test/runTest.js",
"compile": "tsc -p ./",
"lint": "eslint --ext .ts,.tsx .",
"format": "prettier --write --ignore-path .gitignore '**/*'"
},
"dependencies": {
"@vscode/test-cli": "^0.0.11",
"axios": "^1.1.2",
"globby": "^14.1.0",
"ignore": "^7.0.4",
@ -1940,11 +1956,15 @@
"simple-git": "^3.28.0"
},
"devDependencies": {
"@types/jest": "^29.5.14",
"@babel/types": "^7.28.4",
"@types/micromatch": "^4.0.9",
"@types/mocha": "^10.0.10",
"@types/node": "^18.0.0",
"@types/picomatch": "^4.0.0",
"@types/vscode": "^1.100.0",
"@vscode/test-electron": "^2.5.2",
"glob": "^11.0.3",
"mocha": "^11.7.4",
"typescript": "^4.8.0",
"webpack": "^5.100.2",
"webpack-cli": "^4.10.0"

View file

@ -16,7 +16,6 @@ import { Tools } from "./tools";
import { LlamaAgent } from "./llama-agent";
import {LlamaWebviewProvider} from "./llama-webview-provider"
import * as vscode from "vscode"
import path from "path";
import { Persistence } from "./persistence";
import { ModelService } from "./services/model-service";
import { HfModelStrategy } from "./services/hf-model-strategy";
@ -24,8 +23,14 @@ import { LocalModelStrategy } from "./services/local-model-strategy";
import { ExternalModelStrategy } from "./services/external-model-strategy";
import { EnvService } from "./services/env-service";
import { AgentService } from "./services/agent-service";
import { AgentCommandService } from "./services/agent-command-service";
import { ChatService } from "./services/chat-service";
import { Agent, Chat, Env, LlmModel } from "./types";
import { ModelType, PERSISTENCE_KEYS } from "./constants";
import { ApiKeyService } from "./services/api-key-service";
export class Application {
private static instance: Application;
public configuration: Configuration;
public extraContext: ExtraContext;
@ -51,6 +56,17 @@ export class Application {
public externalModelStrategy: ExternalModelStrategy
public envService: EnvService
public agentService: AgentService
public agentCommandService: AgentCommandService
public chatService: ChatService
public apiKeyService: ApiKeyService
private selectedComplModel: LlmModel = ModelService.emptyModel
private selectedModel: LlmModel = ModelService.emptyModel
private selectedEmbeddingsModel: LlmModel = ModelService.emptyModel
private selectedToolsModel: LlmModel = ModelService.emptyModel
private selectedEnv: Env = {name: ""}
private selectedAgent: Agent = {name: "", systemInstruction: []}
private selectedChat: Chat = {name: "", id: ""}
private constructor(context: vscode.ExtensionContext) {
this.configuration = new Configuration()
@ -78,6 +94,9 @@ export class Application {
this.modelService = new ModelService(this)
this.envService = new EnvService(this)
this.agentService = new AgentService(this)
this.agentCommandService = new AgentCommandService(this)
this.chatService = new ChatService(this)
this.apiKeyService = new ApiKeyService(this)
}
public static getInstance(context: vscode.ExtensionContext): Application {
@ -87,4 +106,93 @@ export class Application {
return Application.instance;
}
getComplModel = (): LlmModel => {
return this.selectedComplModel;
}
getToolsModel = (): LlmModel => {
return this.selectedToolsModel;
}
getChatModel = (): LlmModel => {
return this.selectedModel;
}
getEmbeddingsModel = (): LlmModel => {
return this.selectedEmbeddingsModel;
}
getEnv = (): Env => {
return this.selectedEnv;
}
getAgent = (): Agent => {
return this.selectedAgent;
}
setAgent = (agent: Agent): void => {
this.selectedAgent = agent;
}
getChat = (): Chat => {
return this.selectedChat;
}
setChat = (chat: Chat) => {
this.selectedChat = chat;
}
isComplModelSelected = (): boolean => {
return this.selectedComplModel != undefined && this.selectedComplModel.name. trim() != "";
}
isChatModelSelected = (): boolean => {
return this.selectedModel != undefined && this.selectedModel.name. trim() != "";
}
isToolsModelSelected = (): boolean => {
return this.selectedToolsModel != undefined && this.selectedToolsModel.name. trim() != "";
}
isEmbeddingsModelSelected = (): boolean => {
return this.selectedEmbeddingsModel != undefined && this.selectedToolsModel.name. trim() != "";
}
isEnvSelected = (): boolean => {
return this.selectedEnv != undefined && this.selectedEnv.name. trim() != "";
}
isAgentSelected = (): boolean => {
return this.selectedAgent != undefined && this.selectedAgent.name.trim() != "";
}
isChatSelected = (): boolean => {
return this.selectedChat != undefined && this.selectedChat.name.trim() != "";
}
setSelectedModel = (type: ModelType, model: LlmModel | undefined) => {
switch (type) {
case ModelType.Completion:
this.selectedComplModel = model??ModelService.emptyModel;
break;
case ModelType.Chat:
this.selectedModel = model??ModelService.emptyModel;
break;
case ModelType.Embeddings:
this.selectedEmbeddingsModel = model??ModelService.emptyModel;
break;
case ModelType.Tools:
this.selectedToolsModel = model??ModelService.emptyModel;
break;
}
this.llamaWebviewProvider.updateLlamaView();
}
public setSelectedEnv(env: Env): void {
this.selectedEnv = env;
this.persistence.setValue(PERSISTENCE_KEYS.SELECTED_ENV, env);
this.llamaWebviewProvider.updateLlamaView();
}
}

View file

@ -43,7 +43,7 @@ export class Architect {
}
}
let lastChat = this.app.persistence.getValue(PERSISTENCE_KEYS.SELECTED_CHAT)
if (lastChat) this.app.menu.selectUpdateChat(lastChat)
if (lastChat) this.app.chatService.selectUpdateChat(lastChat)
let lastAgent = this.app.persistence.getValue(PERSISTENCE_KEYS.SELECTED_AGENT)
if (lastAgent) this.app.agentService.selectAgent(lastAgent)
this.app.tools.init()
@ -514,7 +514,7 @@ export class Architect {
private getChatEndpoint() {
let endpoint = this.app.configuration.endpoint_chat;
let chatModel = this.app.menu.getChatModel();
let chatModel = this.app.getChatModel();
if (chatModel && chatModel.endpoint) endpoint = chatModel.endpoint;
return endpoint;
}

View file

@ -53,7 +53,7 @@ export class ChatContext {
this.app.statusbar.showTextInfo(this.app.configuration.getUiText("Filtering chunks with BM25..."))
let topChunksBm25 = this.rankTexts(keywords, Array.from(this.entries.values()), this.app.configuration.rag_max_bm25_filter_chunks)
let topContextChunks: ChunkEntry[];
if ((this.app.menu.getEmbeddingsModel().endpoint && this.app.menu.getEmbeddingsModel().endpoint?.trim() != "")
if ((this.app.getEmbeddingsModel().endpoint && this.app.getEmbeddingsModel().endpoint?.trim() != "")
|| this.app.configuration.endpoint_embeddings.trim() != ""){
this.app.statusbar.showTextInfo(this.app.configuration.getUiText("Filtering chunks with embeddings..."))
topContextChunks = await this.cosineSimilarityRank(query, topChunksBm25, this.app.configuration.rag_max_embedding_filter_chunks);

View file

@ -55,8 +55,8 @@ export class ChatWithAi {
? this.app.configuration.endpoint_chat + "/"
: this.app.configuration.endpoint_tools ? this.app.configuration.endpoint_tools + "/" : "";
let chatModel = this.app.menu.getChatModel();
if (!this.app.menu.isChatModelSelected()) chatModel = this.app.menu.getToolsModel();
let chatModel = this.app.getChatModel();
if (!this.app.isChatModelSelected()) chatModel = this.app.getToolsModel();
if (chatModel.endpoint) {
const chatEndpoint = Utils.trimTrailingSlash(chatModel.endpoint)
targetUrl = chatEndpoint ? chatEndpoint + "/" : "";
@ -64,7 +64,7 @@ export class ChatWithAi {
if (!targetUrl) {
const shouldSelectModel = await Utils.showUserChoiceDialog("Select a chat or tools model run by llama-server or an env with chat or tools model run on llama-server to chat with AI.","Select")
if (shouldSelectModel){
this.app.menu.showEnvView();
this.app.llamaWebviewProvider.showEnvView();
vscode.window.showInformationMessage("After the chat/tools model is loaded, try again opening Chat with AI.")
return;
} else {

View file

@ -84,6 +84,9 @@ export enum AGENT_NAME {
}
export const UI_TEXT_KEYS = {
// Agent command texts
enterName: "Enter agent command name",
// Menu separators and sections
actions: "Actions",
entities: "Entities",

View file

@ -13,8 +13,8 @@ export class Git {
generateCommitMessage = async (): Promise<void> => {
let chatUrl = this.app.configuration.endpoint_chat
if (!chatUrl) chatUrl = this.app.configuration.endpoint_tools;
let chatModel = this.app.menu.getChatModel();
if (!this.app.menu.isChatModelSelected()) chatModel = this.app.menu.getToolsModel();
let chatModel = this.app.getChatModel();
if (!this.app.isChatModelSelected()) chatModel = this.app.getToolsModel();
if (chatModel.endpoint) {
const chatEndpoint = Utils.trimTrailingSlash(chatModel.endpoint)
chatUrl = chatEndpoint ? chatEndpoint + "/" : "";
@ -22,7 +22,7 @@ export class Git {
if (!chatUrl) {
const shouldSelectModel = await Utils.showUserChoiceDialog("Select a chat or tools model or an env with chat or tools model to generate a commit message.","Select")
if (shouldSelectModel){
this.app.menu.showEnvView();
this.app.llamaWebviewProvider.showEnvView();
vscode.window.showInformationMessage("After the chat/tools model is loaded, try again generating commit message.")
return;
}

View file

@ -83,25 +83,17 @@ export const PREDEFINED_LISTS = new Map<string, any>([
"isKeyRequired": false
},
{
"name": "xAI: Grok 4 Fast (free for limited period), context: 2 000 000",
"localStartCommand": "",
"name": "DeepSeek V3.1 (free) 163,800 context (OpenRouter)",
"localStartCommand": "",
"endpoint": "https://openrouter.ai/api",
"aiModel": "deepseek/deepseek-chat-v3.1:free",
"isKeyRequired": true
},
{
"name": "Z.AI: GLM 4.5 Air (free): GLM 4.5 Air - 128.000 context (OpenRouter)",
"endpoint": "https://openrouter.ai/api",
"isKeyRequired": true,
"aiModel": "x-ai/grok-4-fast:free"
},
{
"name": "Sonoma Sky - 2,000,000 context $0/M input tokens $0/M output tokens as of 19.09.25 (OpenRouter)",
"localStartCommand": "",
"endpoint": "https://openrouter.ai/api",
"aiModel": "openrouter/sonoma-sky-alpha",
"isKeyRequired": true
},
{
"name": "Sonoma Dusk - 2,000,000 context $0/M input tokens $0/M output tokens as of 19.09.25 (OpenRouter)",
"localStartCommand": "",
"endpoint": "https://openrouter.ai/api",
"aiModel": "openrouter/sonoma-dusk-alpha",
"isKeyRequired": true
"aiModel": "z-ai/glm-4.5-air:free"
},
{
"name": "Z.AI: GLM 4.5 - 128000 context $0.60/M input tokens $2.20/M output tokens (OpenRouter)",
@ -717,6 +709,57 @@ export const PREDEFINED_LISTS = new Map<string, any>([
"edit_file",
"ask_user"
]
},
{
"name": "Ask",
"description": "This is an agent for questions about source code without changing it.",
"systemInstruction": [
"You are an agent for answering questions about the project and software development in general - please keep going until the users query is completely resolved, before ending your turn and yielding back to the user.",
"Only terminate your turn when you are sure that the question is answered.",
"If you are not sure about anything pertaining to the users request, use your tools to read files and gather the relevant information: do NOT guess or make up an answer.",
"You MUST plan extensively before each function call, and reflect extensively on the outcomes of the previous function calls. DO NOT do this entire process by making function calls only, as this can impair your ability to solve the problem and think insightfully.",
"Do not change add or remove files in the project. Just review, answer questions and suggest solutions.",
"",
"# Workflow",
"",
"## High-Level Problem Solving Strategy",
"",
"1. Understand the problem deeply. Carefully read the issue and think critically about what is required.",
"2. Investigate the codebase. Explore relevant files, search for key functions, and gather context.",
"3. Develop a clear, step-by-step plan. ",
"7. Reflect and validate comprehensively.",
"",
"Refer to the detailed sections below for more information on each step.",
"",
"## 1. Deeply Understand the Problem",
"Carefully read the issue and think hard about a plan to solve it before coding.",
"",
"## 2. Codebase Investigation",
"- Explore relevant files and directories.",
"- Search for key functions, classes, or variables related to the issue.",
"- Read and understand relevant code snippets.",
"- Identify the root cause of the problem.",
"- Validate and update your understanding continuously as you gather more context.",
"",
"## 3. Develop a Detailed Plan",
"- Outline a specific, simple, and verifiable sequence of steps find and answer or a solution to the problem.",
"",
"## 4. Final Verification",
"- Confirm the user query is answerd.",
"- Review your solution for logic correctness and robustness.",
"- Think about potential edge cases or scenarios.",
"- Iterate until you are extremely confident the answer is complete.",
""
],
"tools": [
"run_terminal_command",
"search_source",
"read_file",
"list_directory",
"regex_search",
"get_diff",
"ask_user"
]
}
]],
[PREDEFINED_LISTS_KEYS.AGENT_COMMANDS, [

View file

@ -33,7 +33,7 @@ export class LlamaAgent {
resetMessages = () => {
let systemPromt = this.app.prompts.TOOLS_SYSTEM_PROMPT_ACTION;
if (this.app.menu.isAgentSelected()) systemPromt = this.app.menu.getAgent().systemInstruction.join("\n")
if (this.app.isAgentSelected()) systemPromt = this.app.getAgent().systemInstruction.join("\n")
let worspaceFolder = "";
if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders[0]){
worspaceFolder = " Project root folder: " + vscode.workspace.workspaceFolders[0].uri.fsPath;
@ -64,7 +64,7 @@ export class LlamaAgent {
this.resetMessages();
if (chat){
const currentChat = this.app.menu.getChat();
const currentChat = this.app.getChat();
this.messages = chat.messages??[];
this.logText = chat.log??"";
}
@ -145,7 +145,7 @@ export class LlamaAgent {
this.logText += "***" + query.replace("\n", " \n") + "***" + "\n\n"; // Make sure markdown shows new lines correctly
if (!this.app.menu.isToolsModelSelected()) {
if (!this.app.isToolsModelSelected()) {
vscode.window.showErrorMessage("Error: Tools model is not selected! Select tools model (or orchestra with tools model) if you want to to use Llama Agent.")
this.app.llamaWebviewProvider.setState("AI is stopped")
return "Tools model is not selected"
@ -305,15 +305,15 @@ export class LlamaAgent {
this.logText += " \nAgent session finished. \n\n"
this.app.llamaWebviewProvider.logInUi(this.logText);
this.app.llamaWebviewProvider.setState("AI finished")
let chat = this.app.menu.getChat()
if (!this.app.menu.isChatSelected()){
let chat = this.app.getChat()
if (!this.app.isChatSelected()){
chat.name = this.logText.slice(0, 25);
chat.id = Date.now().toString(36);
chat.description = new Date().toLocaleString() + " " + this.logText.slice(0,150)
}
chat.messages = this.messages;
chat.log = this.logText;
await this.app.menu.selectUpdateChat(chat)
await this.app.chatService.selectUpdateChat(chat)
// Clean up AbortController
this.abortController = null;

View file

@ -283,7 +283,7 @@ export class LlamaServer {
const selectionMessate = "Select a completion model or an env with completion model to use code completion (code suggestions by AI)."
const shouldSelectModel = await Utils.showUserChoiceDialog(selectionMessate, "Select")
if (shouldSelectModel){
this.app.menu.showEnvView();
this.app.llamaWebviewProvider.showEnvView();
vscode.window.showInformationMessage("After the completion model is loaded, try again using code completion.")
return;
} else {
@ -345,7 +345,7 @@ export class LlamaServer {
onDelta?: (delta: string) => void,
abortSignal?: AbortSignal
): Promise<LlamaToolsResponse | undefined> => {
let selectedModel: LlmModel = this.app.menu.getToolsModel();
let selectedModel: LlmModel = this.app.getToolsModel();
let model = this.app.configuration.ai_model;
if (selectedModel?.aiModel !== undefined && selectedModel.aiModel) model = selectedModel.aiModel;
@ -498,7 +498,7 @@ export class LlamaServer {
getEmbeddings = async (text: string): Promise<LlamaEmbeddingsResponse | undefined> => {
try {
let selectedModel: LlmModel = this.app.menu.getEmbeddingsModel();
let selectedModel: LlmModel = this.app.getEmbeddingsModel();
let model = this.app.configuration.ai_model;
if (selectedModel.aiModel) model = selectedModel.aiModel;
@ -742,8 +742,8 @@ export class LlamaServer {
private getChatModelProperties() {
let selectedModel: LlmModel = this.app.menu.getChatModel();
if (!this.app.menu.isChatModelSelected()) selectedModel = this.app.menu.getToolsModel();
let selectedModel: LlmModel = this.app.getChatModel();
if (!this.app.isChatModelSelected()) selectedModel = this.app.getToolsModel();
let model = this.app.configuration.ai_model;
if (selectedModel?.aiModel !== undefined && selectedModel.aiModel) model = selectedModel.aiModel;
@ -768,7 +768,7 @@ export class LlamaServer {
}
private getComplModelProperties() {
const selectedComplModel: LlmModel = this.app.menu.getComplModel();
const selectedComplModel: LlmModel = this.app.getComplModel();
let model = this.app.configuration.ai_model;
if (selectedComplModel?.aiModel !== undefined && selectedComplModel.aiModel) model = selectedComplModel.aiModel;

View file

@ -54,14 +54,14 @@ export class LlamaWebviewProvider implements vscode.WebviewViewProvider {
case 'clearText':
this.app.llamaAgent.resetMessages();
this.app.llamaAgent.resetContextProjectFiles()
await this.app.menu.selectUpdateChat({name:"", id:""})
await this.app.chatService.selectUpdateChat({name:"", id:""})
vscode.commands.executeCommand('llama-vscode.webview.postMessage', {
command: 'updateText',
text: ''
});
break;
case 'showChatsHistory':
this.app.menu.selectChatFromList();
this.app.chatService.selectChatFromList();
break;
case 'configureTools':
await this.app.tools.selectTools()
@ -70,46 +70,46 @@ export class LlamaWebviewProvider implements vscode.WebviewViewProvider {
this.app.llamaAgent.stopAgent();
break;
case 'selectModelWithTools':
await this.app.menu.selectAndSetModel(ModelType.Tools, this.app.configuration.tools_models_list);
await this.app.modelService.selectAndSetModel(ModelType.Tools, this.app.configuration.tools_models_list);
break;
case 'selectModelForChat':
await this.app.menu.selectAndSetModel(ModelType.Chat, this.app.configuration.chat_models_list);
await this.app.modelService.selectAndSetModel(ModelType.Chat, this.app.configuration.chat_models_list);
break;
case 'selectModelForEmbeddings':
await this.app.menu.selectAndSetModel(ModelType.Embeddings, this.app.configuration.embeddings_models_list);
await this.app.modelService.selectAndSetModel(ModelType.Embeddings, this.app.configuration.embeddings_models_list);
break;
case 'selectModelForCompletion':
await this.app.menu.selectAndSetModel(ModelType.Completion, this.app.configuration.completion_models_list);
await this.app.modelService.selectAndSetModel(ModelType.Completion, this.app.configuration.completion_models_list);
break;
case 'deselectCompletionModel':
await this.app.menu.deselectAndClearModel(ModelType.Completion);
await this.app.modelService.deselectAndClearModel(ModelType.Completion);
break;
case 'deselectChatModel':
await this.app.menu.deselectAndClearModel(ModelType.Chat);
await this.app.modelService.deselectAndClearModel(ModelType.Chat);
break;
case 'deselectEmbsModel':
await this.app.menu.deselectAndClearModel(ModelType.Embeddings);
await this.app.modelService.deselectAndClearModel(ModelType.Embeddings);
break;
case 'deselectToolsModel':
await this.app.menu.deselectAndClearModel(ModelType.Tools);
await this.app.modelService.deselectAndClearModel(ModelType.Tools);
break;
case 'deselectAgent':
await this.app.agentService.deselectAgent();
break;
case 'showCompletionModel':
this.app.modelService.showModelDetails(this.app.menu.getComplModel());
this.app.modelService.showModelDetails(this.app.getComplModel());
break;
case 'showChatModel':
this.app.modelService.showModelDetails(this.app.menu.getChatModel());
this.app.modelService.showModelDetails(this.app.getChatModel());
break;
case 'showEmbsModel':
this.app.modelService.showModelDetails(this.app.menu.getEmbeddingsModel());
this.app.modelService.showModelDetails(this.app.getEmbeddingsModel());
break;
case 'showToolsModel':
this.app.modelService.showModelDetails(this.app.menu.getToolsModel());
this.app.modelService.showModelDetails(this.app.getToolsModel());
break;
case 'showAgentDetails':
this.app.agentService.showAgentDetails(this.app.menu.getAgent())
this.app.agentService.showAgentDetails(this.app.getAgent())
break;
case 'selectAgent':
let agentsList = this.app.configuration.agents_list
@ -132,13 +132,13 @@ export class LlamaWebviewProvider implements vscode.WebviewViewProvider {
await this.app.envService.stopEnv();
break;
case 'showEnvView':
this.app.menu.showEnvView();
this.showEnvView();
break;
case 'showAgentView':
this.app.menu.showAgentView();
this.showAgentView();
break;
case 'showSelectedModels':
await this.app.menu.showCurrentEnv();
await this.app.envService.showCurrentEnv();
break;
case 'getFileList':
let fileKeys: string[]
@ -238,7 +238,7 @@ export class LlamaWebviewProvider implements vscode.WebviewViewProvider {
}
private updateEmbsModel() {
const currentEmbeddingsModel: LlmModel = this.app.menu.getEmbeddingsModel();
const currentEmbeddingsModel: LlmModel = this.app.getEmbeddingsModel();
vscode.commands.executeCommand('llama-vscode.webview.postMessage', {
command: 'updateEmbeddingsModel',
model: currentEmbeddingsModel.name || 'No model selected'
@ -246,7 +246,7 @@ export class LlamaWebviewProvider implements vscode.WebviewViewProvider {
}
private updateChatModel() {
const currentChatModel: LlmModel = this.app.menu.getChatModel();
const currentChatModel: LlmModel = this.app.getChatModel();
vscode.commands.executeCommand('llama-vscode.webview.postMessage', {
command: 'updateChatModel',
model: currentChatModel.name || 'No model selected'
@ -254,7 +254,7 @@ export class LlamaWebviewProvider implements vscode.WebviewViewProvider {
}
private updateToolsModel() {
const currentToolsModel: LlmModel = this.app.menu.getToolsModel();
const currentToolsModel: LlmModel = this.app.getToolsModel();
vscode.commands.executeCommand('llama-vscode.webview.postMessage', {
command: 'updateToolsModel',
model: currentToolsModel.name || 'No model selected'
@ -262,7 +262,7 @@ export class LlamaWebviewProvider implements vscode.WebviewViewProvider {
}
private updateComplsModel() {
const currentToolsModel: LlmModel = this.app.menu.getComplModel();
const currentToolsModel: LlmModel = this.app.getComplModel();
vscode.commands.executeCommand('llama-vscode.webview.postMessage', {
command: 'updateCompletionModel',
model: currentToolsModel.name || 'No model selected'
@ -270,7 +270,7 @@ export class LlamaWebviewProvider implements vscode.WebviewViewProvider {
}
private updateAgent() {
const currentAgent: Agent = this.app.menu.getAgent();
const currentAgent: Agent = this.app.getAgent();
vscode.commands.executeCommand('llama-vscode.webview.postMessage', {
command: 'updateAgent',
agent: currentAgent.name || 'No agent selected'
@ -278,7 +278,7 @@ export class LlamaWebviewProvider implements vscode.WebviewViewProvider {
}
private updateEnv() {
const currentEnv: Env = this.app.menu.getEnv();
const currentEnv: Env = this.app.getEnv();
vscode.commands.executeCommand('llama-vscode.webview.postMessage', {
command: 'updateEnv',
model: currentEnv.name || 'No env selected'
@ -313,6 +313,26 @@ export class LlamaWebviewProvider implements vscode.WebviewViewProvider {
});
}
public async showAgentView() {
let isModelAvailable = await this.app.modelService.checkForToolsModel();
if (isModelAvailable) {
vscode.commands.executeCommand('extension.showLlamaWebview');
this.updateLlamaView();
setTimeout(() => {
if (this.webview) {
this.webview.webview.postMessage({
command: 'focusTextarea'
});
}
}, 100);
}
}
public showEnvView() {
vscode.commands.executeCommand('extension.showLlamaWebview');
setTimeout(() => this.setView("addenv"), 500);
}
public updateLlamaView() {
this.updateToolsModel();
this.updateChatModel();

File diff suppressed because it is too large Load diff

View file

@ -171,7 +171,7 @@ filename.py
2. Format Rules:
- The first line must be a code fence opening marker (\`\`\`diff)
- The second line must contain ONLY the file path, exactly as shown to you
- The SEARCH block must contain the EXACT lines with correct spacingto be replaced from the file, the lines should be in the same order. Never skip or shorten peaces of the content to be replaced!
- The SEARCH block must contain the EXACT lines with correct number of spaces or tabs before and after the text of each line, the lines should be in the same order. Never skip or shorten peaces of the content to be replaced!
- The REPLACE block contains the new content
- End with a code fence closing marker (\`\`\`)
- Include enough context in the SEARCH block to uniquely identify the section to change

View file

@ -0,0 +1,198 @@
import { Application } from "../application";
import vscode, { QuickPickItem } from "vscode";
import { Agent, AgentCommand } from "../types";
import { Utils } from "../utils";
import * as fs from 'fs';
import * as path from 'path';
import { PREDEFINED_LISTS_KEYS, SETTING_NAME_FOR_LIST, UI_TEXT_KEYS } from "../constants";
import { PREDEFINED_LISTS } from "../lists";
export class AgentCommandService {
private app: Application;
constructor(app: Application) {
this.app = app;
}
public getAgentCommandsActions(): vscode.QuickPickItem[] {
return [
{
label: this.app.configuration.getUiText(UI_TEXT_KEYS.addAgentCommand) ?? ""
},
{
label: this.app.configuration.getUiText(UI_TEXT_KEYS.viewAgentCommandDetails) ?? ""
},
{
label: this.app.configuration.getUiText(UI_TEXT_KEYS.deleteAgentCommand) ?? ""
},
{
label: this.app.configuration.getUiText(UI_TEXT_KEYS.exportAgentCommand) ?? ""
},
{
label: this.app.configuration.getUiText(UI_TEXT_KEYS.importAgentCommand) ?? ""
},
];
}
async processActions(selected: QuickPickItem): Promise<void> {
const label = selected.label;
try {
switch (label) {
case this.app.configuration.getUiText(UI_TEXT_KEYS.addAgentCommand):
await this.addAgentCommand(this.app.configuration.agent_commands, SETTING_NAME_FOR_LIST.AGENT_COMMANDS);
break;
case this.app.configuration.getUiText(UI_TEXT_KEYS.deleteAgentCommand):
await this.deleteAgentCommandFromList(this.app.configuration.agent_commands, SETTING_NAME_FOR_LIST.AGENT_COMMANDS);
break;
case this.app.configuration.getUiText(UI_TEXT_KEYS.viewAgentCommandDetails):
await this.viewAgentCommandFromList(this.app.configuration.agent_commands);
break;
case this.app.configuration.getUiText(UI_TEXT_KEYS.exportAgentCommand):
await this.exportAgentCommandFromList(this.app.configuration.agent_commands);
break;
case this.app.configuration.getUiText(UI_TEXT_KEYS.importAgentCommand):
await this.importAgentCommandToList(this.app.configuration.agent_commands, SETTING_NAME_FOR_LIST.AGENT_COMMANDS);
break;
default:
vscode.window.showWarningMessage("Unknown action");
}
} catch (error) {
vscode.window.showErrorMessage(`Error processing action: ${error instanceof Error ? error.message : 'Unknown error'}`);
console.error(error);
}
}
private async addAgentCommand(commands: AgentCommand[], settingName: string): Promise<void> {
const name = await vscode.window.showInputBox({
prompt: this.app.configuration.getUiText(UI_TEXT_KEYS.enterName) ?? "Enter agent command name",
validateInput: (value) => value ? null : "Name is required"
});
if (!name) return;
const command = await vscode.window.showInputBox({
prompt: "Enter the command",
validateInput: (value) => value ? null : "Command is required"
});
if (!command) return;
const description = await vscode.window.showInputBox({ prompt: "Enter description (optional)" });
const newCommand: AgentCommand = { name, prompt: [command], description: description || "" };
await this.persistAgentCommandToSetting(newCommand, this.app.configuration.agent_commands, settingName);
}
private async deleteAgentCommandFromList(agentCommands: AgentCommand[], settingName: string) {
const modelsItems: QuickPickItem[] = Utils.getStandardQpList(agentCommands, "");
const model = await vscode.window.showQuickPick(modelsItems);
if (model) {
let modelIndex = parseInt(model.label.split(". ")[0], 10) - 1;
const shoulDeleteModel = await Utils.confirmAction("Are you sure you want to delete the agent command below?",
this.getAgentCommandDetailsAsString(agentCommands[modelIndex])
);
if (shoulDeleteModel) {
agentCommands.splice(modelIndex, 1);
this.app.configuration.updateConfigValue(settingName, agentCommands);
vscode.window.showInformationMessage("The agent command is deleted.")
}
}
}
private async viewAgentCommandFromList(agentCommands: any[]) {
let allAgentCommands = agentCommands.concat(PREDEFINED_LISTS.get(PREDEFINED_LISTS_KEYS.AGENT_COMMANDS) as AgentCommand[])
let agentComandItems: QuickPickItem[] = Utils.getStandardQpList(agentCommands, "");
agentComandItems = agentComandItems.concat(Utils.getStandardQpList(PREDEFINED_LISTS.get(PREDEFINED_LISTS_KEYS.AGENT_COMMANDS) as AgentCommand[], "(predefined) ", agentCommands.length));
let agentCommand = await vscode.window.showQuickPick(agentComandItems);
if (agentCommand) {
let agentCommandIndex = parseInt(agentCommand.label.split(". ")[0], 10) - 1;
let selectedAgentCommand = allAgentCommands[agentCommandIndex];
await this.showAgentCommandDetails(selectedAgentCommand);
}
}
public async showAgentCommandDetails(selectedAgentCommand: any) {
await Utils.showOkDialog(
this.getAgentCommandDetailsAsString(selectedAgentCommand)
);
}
private async persistAgentCommandToSetting(newAgentCommand: AgentCommand, agentCommands: any[], settingName: string) {
let modelDetails = this.getAgentCommandDetailsAsString(newAgentCommand);
const shouldAddModel = await Utils.confirmAction("A new agent command will be added. Do you want to add the agent command?", modelDetails);
if (shouldAddModel) {
agentCommands.push(newAgentCommand);
this.app.configuration.updateConfigValue(settingName, agentCommands);
vscode.window.showInformationMessage("The agent command is added.");
}
}
private async exportAgentCommandFromList(agentCommands: any[]) {
let allAgentCommands = agentCommands.concat(PREDEFINED_LISTS.get(PREDEFINED_LISTS_KEYS.AGENT_COMMANDS) as Agent[])
let agentComandItems: QuickPickItem[] = Utils.getStandardQpList(agentCommands, "");
agentComandItems = agentComandItems.concat(Utils.getStandardQpList(PREDEFINED_LISTS.get(PREDEFINED_LISTS_KEYS.AGENT_COMMANDS) as Agent[], "(predefined) ", agentCommands.length));
let agentCommand = await vscode.window.showQuickPick(agentComandItems);
if (agentCommand) {
let modelIndex = parseInt(agentCommand.label.split(". ")[0], 10) - 1;
let selectedAgentCommand = allAgentCommands[modelIndex];
let shouldExport = await Utils.showYesNoDialog("Do you want to export the following agent command? \n\n" +
this.getAgentCommandDetailsAsString(selectedAgentCommand)
);
if (shouldExport){
const uri = await vscode.window.showSaveDialog({
defaultUri: vscode.Uri.file(path.join(vscode.workspace.rootPath || '', selectedAgentCommand.name+'.json')),
filters: {
'Agent Command Files': ['json'],
'All Files': ['*']
},
saveLabel: 'Export Agent Command'
});
if (!uri) {
return;
}
const jsonContent = JSON.stringify(selectedAgentCommand, null, 2);
fs.writeFileSync(uri.fsPath, jsonContent, 'utf8');
vscode.window.showInformationMessage("Agent command is saved.")
}
}
}
private async importAgentCommandToList(agentCommands: any[], settingName: string) {
let name = "";
const uris = await vscode.window.showOpenDialog({
canSelectMany: false,
openLabel: 'Import Agent Command',
filters: {
'Agent Command Files': ['json'],
'All Files': ['*']
},
});
if (!uris || uris.length === 0) {
return;
}
const filePath = uris[0].fsPath;
const fileContent = fs.readFileSync(filePath, 'utf8');
const newAgentCommand = JSON.parse(fileContent);
// Sanitize imported agent command
if (newAgentCommand.name) newAgentCommand.name = this.app.modelService.sanitizeInput(newAgentCommand.name);
if (newAgentCommand.description) newAgentCommand.description = this.app.modelService.sanitizeInput(newAgentCommand.description);
if (newAgentCommand.prompt) newAgentCommand.prompt = newAgentCommand.prompt.map((s: string) => this.app.modelService.sanitizeInput(s));
if (newAgentCommand.context) newAgentCommand.context = newAgentCommand.context.map((s: string) => this.app.modelService.sanitizeInput(s));
await this.persistAgentCommandToSetting(newAgentCommand, agentCommands, settingName);
}
private getAgentCommandDetailsAsString(selectedAgentCommand: AgentCommand): string {
return "Agent command details: " +
"\nname: " + selectedAgentCommand.name +
"\ndescription: " + selectedAgentCommand.description +
"\nprompt: \n" + selectedAgentCommand.prompt.join("\n") +
"\n\ncontext: " + (selectedAgentCommand.context ? selectedAgentCommand.context.join(", ") : "");
}
}

View file

@ -6,7 +6,6 @@ import { Agent } from "../types";
import { Utils } from "../utils";
import * as fs from "fs";
import * as path from "path";
import { Configuration } from "../configuration";
import { PREDEFINED_LISTS } from "../lists";
import { UI_TEXT_KEYS, PERSISTENCE_KEYS, SETTING_NAME_FOR_LIST, PREDEFINED_LISTS_KEYS } from "../constants";
@ -96,7 +95,7 @@ export class AgentService {
}
async selectAgent(agent: Agent): Promise<void> {
this.app.menu.setSelectedAgent(agent);
this.app.setAgent(agent);
const allTools = Array.from(this.app.tools.toolsFunc.keys());
for (let toolName of allTools) {
this.app.configuration.updateConfigValue(`tool_${toolName}_enabled`, agent.tools?.includes(toolName) ?? false);
@ -110,7 +109,7 @@ export class AgentService {
async deselectAgent(): Promise<void> {
const emptyAgent = { name: "", systemInstruction: [] };
this.app.menu.setSelectedAgent(emptyAgent);
this.app.setAgent(emptyAgent);
const allTools = Array.from(this.app.tools.toolsFunc.keys());
for (let toolName of allTools) {
this.app.configuration.updateConfigValue(`tool_${toolName}_enabled`, true);

View file

@ -0,0 +1,70 @@
import * as fs from 'fs';
import * as path from 'path';
import { Application } from "../application";
import { Chat } from "../types";
import { PERSISTENCE_KEYS, UI_TEXT_KEYS } from "../constants";
import vscode, { QuickPickItem } from "vscode";
import { Utils } from '../utils';
export class ApiKeyService {
private app: Application;
constructor(application: Application) {
this.app = application;
}
public getApiKeyActions(): vscode.QuickPickItem[] {
return [
{
label: this.app.configuration.getUiText(UI_TEXT_KEYS.addAPIKey) ?? ""
},
{
label: this.app.configuration.getUiText(UI_TEXT_KEYS.editDeleteAPIKey) ?? ""
},
];
}
processApiKeyActions = async (selected:vscode.QuickPickItem) => {
switch (selected.label) {
case this.app.configuration.getUiText(UI_TEXT_KEYS.editDeleteAPIKey):
const apiKeysMap = this.app.persistence.getAllApiKeys();
const apiKeysQuickPick = Array.from(apiKeysMap.entries()).map(([key, value]) => ({
label: key,
description: "..." + value.slice(-5)
}));
const selectedItem = await vscode.window.showQuickPick(apiKeysQuickPick);
if (selectedItem) {
let result = await vscode.window.showInputBox({
placeHolder: 'Enter your new api key for ' + selectedItem.label + ". Leave empty to remove it.",
prompt: 'your api key',
value: ''
})
result = this.app.modelService.sanitizeInput(result || '');
if (!result || result === "") this.app.persistence.deleteApiKey(selectedItem.label);
else this.app.persistence.setApiKey(selectedItem.label, result);
}
break;
case this.app.configuration.getUiText(UI_TEXT_KEYS.addAPIKey) ?? "":
let endpoint = await vscode.window.showInputBox({
placeHolder: 'Enter the endpoint, exactly as in the model',
prompt: 'Endpoint (url)',
value: ''
})
endpoint = this.app.modelService.sanitizeInput(endpoint || '');
let apiKey = await vscode.window.showInputBox({
placeHolder: 'Enter your new api key for ' + endpoint,
prompt: 'your api key',
value: ''
})
apiKey = this.app.modelService.sanitizeInput(apiKey || '');
if (endpoint && apiKey)
{
this.app.persistence.setApiKey(endpoint, apiKey);
vscode.window.showInformationMessage("Api key is added.")
}
else vscode.window.showErrorMessage("API key was not added. Please provide both endpoint and API key.")
break;
}
}
}

View file

@ -0,0 +1,183 @@
import * as fs from 'fs';
import * as path from 'path';
import { Application } from "../application";
import { Chat } from "../types";
import { PERSISTENCE_KEYS, UI_TEXT_KEYS } from "../constants";
import vscode, { QuickPickItem } from "vscode";
import { Utils } from '../utils';
export class ChatService {
private app: Application;
constructor(application: Application) {
this.app = application;
}
selectChatFromList = async () => {
let chatsList = this.app.persistence.getChats()
if (!chatsList || chatsList.length == 0){
vscode.window.showInformationMessage("No chats in the history.")
return;
}
const chatsItems: QuickPickItem[] = Utils.getStandardQpList(chatsList, "");
const chat = await vscode.window.showQuickPick(chatsItems);
if (chat) {
let futureChat: Chat;
futureChat = chatsList[parseInt(chat.label.split(". ")[0], 10) - 1]
if(!futureChat){
vscode.window.showWarningMessage("No chat selected.");
return;
}
await this.selectUpdateChat(futureChat)
}
}
updateChatHistory = async () => {
// if chat exists - update it, otherwise, just add it
if (this.app.isChatSelected()){
let chatToAdd = this.app.getChat();
await this.addChatToHistory(chatToAdd);
}
}
selectUpdateChat = async (chatToSelect: Chat) => {
if (chatToSelect.id != this.app.getChat().id){
await this.updateChatHistory();
this.app.setChat(chatToSelect);
await this.app.persistence.setValue(PERSISTENCE_KEYS.SELECTED_CHAT, chatToSelect);
this.app.llamaAgent.selectChat(chatToSelect);
this.app.llamaWebviewProvider.updateLlamaView();
} else {
this.app.setChat(chatToSelect);
await this.app.persistence.setValue(PERSISTENCE_KEYS.SELECTED_CHAT, this.app.getChat());
}
}
deleteChatFromList = async (chatList: Chat[]) => {
const chatsItems: QuickPickItem[] = Utils.getStandardQpList(chatList, "");
const chat = await vscode.window.showQuickPick(chatsItems);
if (chat) {
const shoulDeleteChat = await Utils.confirmAction("Are you sure you want to delete the chat below?",
"name: " + chat.label + "\ndescription: " + chat.description
);
if (shoulDeleteChat) {
let chatToDelIndex = parseInt(chat.label.split(". ")[0], 10) - 1
chatList.splice(chatToDelIndex, 1);
await this.app.persistence.setChats(chatList);
vscode.window.showInformationMessage("The chat is deleted: " + chat.label)
}
}
}
public getChatActions(): vscode.QuickPickItem[] {
return [
{
label: this.app.configuration.getUiText(UI_TEXT_KEYS.selectStartChat) ?? ""
},
{
label: this.app.configuration.getUiText(UI_TEXT_KEYS.deleteChat) ?? ""
},
{
label: this.app.configuration.getUiText(UI_TEXT_KEYS.exportChat) ?? ""
},
{
label: this.app.configuration.getUiText(UI_TEXT_KEYS.importChat) ?? ""
},
];
}
public processChatActions = async (selected:vscode.QuickPickItem) => {
switch (selected.label) {
case this.app.configuration.getUiText(UI_TEXT_KEYS.selectStartChat):
await this.selectChatFromList();
break;
case this.app.configuration.getUiText(UI_TEXT_KEYS.deleteChat):
await this.deleteChatFromList(this.app.persistence.getChats());
break;
case this.app.configuration.getUiText(UI_TEXT_KEYS.exportChat):
await this.exportChatFromList(this.app.persistence.getChats())
break;
case this.app.configuration.getUiText(UI_TEXT_KEYS.importChat):
await this.importChatToList()
break;
}
}
private async importChatToList() {
let name = "";
const uris = await vscode.window.showOpenDialog({
canSelectMany: false,
openLabel: 'Import Chat',
filters: {
'Chat Files': ['json'],
'All Files': ['*']
},
});
if (!uris || uris.length === 0) {
return;
}
const filePath = uris[0].fsPath;
const fileContent = fs.readFileSync(filePath, 'utf8');
const newChat = JSON.parse(fileContent);
// Sanitize imported chat
if (newChat.name) newChat.name = this.app.modelService.sanitizeInput(newChat.name);
if (newChat.description) newChat.description = this.app.modelService.sanitizeInput(newChat.description);
if (newChat.messages) {
newChat.messages = newChat.messages.map((msg: any) => ({
...msg,
content: this.app.modelService.sanitizeInput(msg.content || ''),
role: this.app.modelService.sanitizeInput(msg.role || '')
}));
}
await this.addChatToHistory(newChat);
}
private async exportChatFromList(chatsList: any[]) {
const chatsItems: QuickPickItem[] = Utils.getStandardQpList(chatsList, "");
let chat = await vscode.window.showQuickPick(chatsItems);
if (chat) {
let modelIndex = parseInt(chat.label.split(". ")[0], 10) - 1;
let selectedChat = chatsList[modelIndex];
let shouldExport = await Utils.showYesNoDialog("Do you want to export the following chat? \n\n" +
"name: " + chat.label +
"\ndescription: " + chat.description
);
if (shouldExport){
const uri = await vscode.window.showSaveDialog({
defaultUri: vscode.Uri.file(path.join(vscode.workspace.rootPath || '', selectedChat.name+'.json')),
filters: {
'Chat Files': ['json'],
'All Files': ['*']
},
saveLabel: 'Export Chat'
});
if (!uri) {
return;
}
const jsonContent = JSON.stringify(selectedChat, null, 2);
fs.writeFileSync(uri.fsPath, jsonContent, 'utf8');
vscode.window.showInformationMessage("Chat is saved.")
}
}
}
private async addChatToHistory(chatToAdd: Chat) {
let chats = this.app.persistence.getChats();
if (!chats) chats = [];
const index = chats.findIndex((ch: Chat) => ch.id === chatToAdd.id);
if (index !== -1) {
chats.splice(index, 1);
}
chats.push(chatToAdd);
if (chats.length > this.app.configuration.chats_max_history) chats.shift();
await this.app.persistence.setChats(chats);
vscode.window.showInformationMessage("The chat '" + chatToAdd.name + "' is added/updated.");
}
}

View file

@ -2,11 +2,10 @@
import * as vscode from "vscode";
import { QuickPickItem } from "vscode";
import { Application } from "../application";
import { Env, Agent, LlmModel, ModelTypeDetails } from "../types";
import { Env } from "../types";
import { Utils } from "../utils";
import * as fs from "fs";
import * as path from "path";
import { Configuration } from "../configuration";
import { PREDEFINED_LISTS } from "../lists";
import { ModelType, UI_TEXT_KEYS, PERSISTENCE_KEYS, SETTING_NAME_FOR_LIST, PREDEFINED_LISTS_KEYS } from "../constants";
@ -53,7 +52,7 @@ export class EnvService {
await this.selectEnv(this.app.configuration.envs_list, true);
break;
case this.app.configuration.getUiText(UI_TEXT_KEYS.addEnv):
this.app.menu.showEnvView();
this.app.llamaWebviewProvider.showEnvView();
break;
break;
case this.app.configuration.getUiText(UI_TEXT_KEYS.deleteEnv):
@ -104,11 +103,11 @@ export class EnvService {
async selectStartEnv(env: Env, confirm: boolean = false): Promise<void> {
// Get current state for inheritance
const currentComplModel = this.app.menu.getComplModel();
const currentChatModel = this.app.menu.getChatModel();
const currentEmbeddingsModel = this.app.menu.getEmbeddingsModel();
const currentToolsModel = this.app.menu.getToolsModel();
const currentAgent = this.app.menu.getAgent();
const currentComplModel = this.app.getComplModel();
const currentChatModel = this.app.getChatModel();
const currentEmbeddingsModel = this.app.getEmbeddingsModel();
const currentToolsModel = this.app.getToolsModel();
const currentAgent = this.app.getAgent();
const currentRagEnabled = this.app.configuration.rag_enabled;
const currentEnvStartLastUsed = this.app.configuration.env_start_last_used;
const currentComplEnabled = this.app.configuration.enabled;
@ -142,7 +141,7 @@ export class EnvService {
if (shouldSelect && env) {
// Set completion model (inherit if not specified)
const complModel = env.completion ?? currentComplModel;
this.app.menu.setSelectedModel(ModelType.Completion, complModel);
this.app.setSelectedModel(ModelType.Completion, complModel);
if (complModel && complModel.name.trim() !== "") {
await this.app.modelService.addApiKey(complModel);
if (complModel.localStartCommand) {
@ -152,7 +151,7 @@ export class EnvService {
// Set chat model
const chatModel = env.chat ?? currentChatModel;
this.app.menu.setSelectedModel(ModelType.Chat, chatModel);
this.app.setSelectedModel(ModelType.Chat, chatModel);
if (chatModel && chatModel.name.trim() !== "") {
await this.app.modelService.addApiKey(chatModel);
if (chatModel.localStartCommand) {
@ -162,7 +161,7 @@ export class EnvService {
// Set embeddings model
const embedModel = env.embeddings ?? currentEmbeddingsModel;
this.app.menu.setSelectedModel(ModelType.Embeddings, embedModel);
this.app.setSelectedModel(ModelType.Embeddings, embedModel);
if (embedModel && embedModel.name.trim() !== "") {
await this.app.modelService.addApiKey(embedModel);
if (embedModel.localStartCommand) {
@ -172,7 +171,7 @@ export class EnvService {
// Set tools model
const toolsModel = env.tools ?? currentToolsModel;
this.app.menu.setSelectedModel(ModelType.Tools, toolsModel);
this.app.setSelectedModel(ModelType.Tools, toolsModel);
if (toolsModel && toolsModel.name.trim() !== "") {
await this.app.modelService.addApiKey(toolsModel);
if (toolsModel.localStartCommand) {
@ -198,7 +197,7 @@ export class EnvService {
}
// Set selected env
this.app.menu.setSelectedEnv(env);
this.app.setSelectedEnv(env);
this.app.llamaWebviewProvider.updateLlamaView();
}
@ -228,11 +227,11 @@ export class EnvService {
description = this.app.modelService.sanitizeInput(description || '');
// Inherit from current state
const currentComplModel = this.app.menu.getComplModel();
const currentChatModel = this.app.menu.getChatModel();
const currentEmbeddingsModel = this.app.menu.getEmbeddingsModel();
const currentToolsModel = this.app.menu.getToolsModel();
const currentAgent = this.app.menu.getAgent();
const currentComplModel = this.app.getComplModel();
const currentChatModel = this.app.getChatModel();
const currentEmbeddingsModel = this.app.getEmbeddingsModel();
const currentToolsModel = this.app.getToolsModel();
const currentAgent = this.app.getAgent();
let newEnv: Env = {
name: name,
@ -292,15 +291,15 @@ export class EnvService {
async stopEnv(): Promise<void> {
await this.app.llamaServer.killFimCmd();
this.app.menu.setSelectedModel(ModelType.Completion, { name: "", localStartCommand: "" });
this.app.setSelectedModel(ModelType.Completion, { name: "", localStartCommand: "" });
await this.app.llamaServer.killChatCmd();
this.app.menu.setSelectedModel(ModelType.Chat, { name: "", localStartCommand: "" });
this.app.setSelectedModel(ModelType.Chat, { name: "", localStartCommand: "" });
await this.app.llamaServer.killEmbeddingsCmd();
this.app.menu.setSelectedModel(ModelType.Embeddings, { name: "", localStartCommand: "" });
this.app.setSelectedModel(ModelType.Embeddings, { name: "", localStartCommand: "" });
await this.app.llamaServer.killToolsCmd();
this.app.menu.setSelectedModel(ModelType.Tools, { name: "", localStartCommand: "" });
this.app.setSelectedModel(ModelType.Tools, { name: "", localStartCommand: "" });
await this.app.agentService.deselectAgent();
this.app.menu.setSelectedEnv({ name: "" });
this.app.setSelectedEnv({ name: "" });
this.app.llamaWebviewProvider.updateLlamaView();
vscode.window.showInformationMessage("Env, models and agent are deselected.")
}
@ -454,4 +453,38 @@ export class EnvService {
}
return items;
}
public showCurrentEnv() {
Utils.showOkDialog(this.getSelectionsAsString());
}
private getSelectionsAsString() {
return "Selected env and models: " +
"\nenv: " + this.app.getEnv().name +
"\nenv description: " + this.app.getEnv().description +
"\n\ncompletion model: " +
"\nname: " + this.app.getComplModel?.name +
"\nlocal start command: " + this.app.getComplModel().localStartCommand +
"\nendpoint: " + this.app.getComplModel().endpoint +
"\nmodel name for provider: " + this.app.getComplModel().aiModel +
"\napi key required: " + this.app.getComplModel().isKeyRequired +
"\n\nchat model: " +
"\nname: " + this.app.getChatModel().name +
"\nlocal start command: " + this.app.getChatModel().localStartCommand +
"\nendpoint: " + this.app.getChatModel().endpoint +
"\nmodel name for provider: " + this.app.getChatModel().aiModel +
"\napi key required: " + this.app.getChatModel().isKeyRequired +
"\n\nembeddings model: " +
"\nname: " + this.app.getEmbeddingsModel().name +
"\nlocal start command: " + this.app.getEmbeddingsModel().localStartCommand +
"\nendpoint: " + this.app.getEmbeddingsModel().endpoint +
"\nmodel name for provider: " + this.app.getEmbeddingsModel().aiModel +
"\napi key required: " + this.app.getEmbeddingsModel().isKeyRequired +
"\n\ntools model: " +
"\nname: " + this.app.getToolsModel().name +
"\nlocal start command: " + this.app.getToolsModel().localStartCommand +
"\nendpoint: " + this.app.getToolsModel().endpoint +
"\nmodel name for provider: " + this.app.getToolsModel().aiModel +
"\napi key required: " + this.app.getToolsModel().isKeyRequired;
}
}

View file

@ -1,14 +1,7 @@
import * as vscode from "vscode";
import { QuickPickItem } from "vscode";
import { Application } from "../application";
import { IAddStrategy, LlmModel, ModelTypeDetails } from "../types";
import { Utils } from "../utils";
import * as axios from "axios";
import { ModelType, UI_TEXT_KEYS, HF_MODEL_TEMPLATES, SETTING_TO_MODEL_TYPE, MODEL_TYPE_CONFIG } from "../constants";
import * as path from "path";
import * as fs from "fs";
import { Configuration } from "../configuration";
export class ExternalModelStrategy implements IAddStrategy {
private app: Application;

View file

@ -4,7 +4,7 @@ import { Application } from "../application";
import { IAddStrategy, LlmModel, ModelTypeDetails } from "../types";
import { Utils } from "../utils";
import * as axios from "axios";
import { ModelType, UI_TEXT_KEYS, HF_MODEL_TEMPLATES, SETTING_TO_MODEL_TYPE, MODEL_TYPE_CONFIG } from "../constants";
import { HF_MODEL_TEMPLATES, SETTING_TO_MODEL_TYPE } from "../constants";
interface HuggingfaceModel {
modelId: string;

View file

@ -1,12 +1,8 @@
import * as vscode from "vscode";
import { QuickPickItem } from "vscode";
import { Application } from "../application";
import { IAddStrategy, LlmModel, ModelTypeDetails } from "../types";
import { Utils } from "../utils";
import * as axios from "axios";
import { ModelType, UI_TEXT_KEYS, HF_MODEL_TEMPLATES, SETTING_TO_MODEL_TYPE, MODEL_TYPE_CONFIG } from "../constants";
export class LocalModelStrategy implements IAddStrategy {
private app: Application;

View file

@ -3,14 +3,14 @@ import { QuickPickItem } from "vscode";
import { Application } from "../application";
import { IAddStrategy, LlmModel, ModelTypeDetails } from "../types";
import { Utils } from "../utils";
import * as axios from "axios";
import { ModelType, UI_TEXT_KEYS, HF_MODEL_TEMPLATES, SETTING_TO_MODEL_TYPE, MODEL_TYPE_CONFIG } from "../constants";
import { ModelType, UI_TEXT_KEYS, MODEL_TYPE_CONFIG } from "../constants";
import * as path from "path";
import * as fs from "fs";
import { Configuration } from "../configuration";
import { PREDEFINED_LISTS } from "../lists";
export class ModelService {
public static readonly emptyModel = {name: ""};
private app: Application;
private strategies: Record<string, IAddStrategy>;
@ -77,6 +77,14 @@ export class ModelService {
}));
}
public async processModelActions(modelType: ModelType) {
let modelActions: vscode.QuickPickItem[] = this.getActions(modelType);
let actionSelected = await vscode.window.showQuickPick(modelActions);
if (actionSelected) {
await this.processActions(modelType, actionSelected);
}
}
async processActions(type: ModelType, selected: vscode.QuickPickItem): Promise<void> {
const details = this.getTypeDetails(type);
const actionMap = this.getActionMap(type);
@ -173,7 +181,7 @@ export class ModelService {
public async selectStartModel(model: LlmModel, type: ModelType, details: ModelTypeDetails) {
await this.addApiKey(model);
this.app.menu.setSelectedModel(type, model);
this.app.setSelectedModel(type, model);
await details.killCmd();
if (model.localStartCommand) await details.shellCmd(this.sanitizeCommand(model.localStartCommand ?? ""));
@ -288,7 +296,7 @@ export class ModelService {
public async deselectModel(type: ModelType, details: ModelTypeDetails): Promise<void> {
await details.killCmd();
this.app.menu.clearModel(type);
this.clearModel(type);
}
getDetails(model: LlmModel): string {
@ -371,4 +379,45 @@ export class ModelService {
public sanitizeInput(input: string): string {
return input ? input.trim() : '';
}
clearModel = (type: ModelType) => {
this.app.setSelectedModel(type, ModelService.emptyModel);
this.app.llamaWebviewProvider.updateLlamaView();
}
public async deselectAndClearModel(modelType: ModelType) {
await this.deselectModel(modelType, this.getTypeDetails(modelType));
this.clearModel(modelType);
this.app.llamaWebviewProvider.updateLlamaView();
}
public async selectAndSetModel(modelType: ModelType, modelsList: LlmModel[]) {
let model = await this.app.modelService.selectModel(modelType, modelsList);
this.app.setSelectedModel(modelType, model);
}
public getEmptyModel(): LlmModel {
return ModelService.emptyModel
}
public async checkForToolsModel() {
let toolsModel = this.app.getToolsModel();
let targetUrl = this.app.configuration.endpoint_tools ? this.app.configuration.endpoint_tools + "/" : "";
if (toolsModel && toolsModel.endpoint) {
const toolsEndpoint = Utils.trimTrailingSlash(toolsModel.endpoint);
targetUrl = toolsEndpoint ? toolsEndpoint + "/" : "";
}
if (!targetUrl) {
const shouldSelectEnv = await Utils.showUserChoiceDialog("Select a tools model or an env with tools model to use Llama Agent.", "Select");
if (shouldSelectEnv) {
// await this.app.menu.selectEnvFromList(this.app.configuration.envs_list.filter(item => item.tools != undefined && item.tools.name));
this.app.llamaWebviewProvider.showEnvView()
vscode.window.showInformationMessage("After the tools model is loaded, try again opening llama agent.");
} else {
vscode.window.showErrorMessage("No endpoint for the tools model. Select an env with tools model or enter the endpoint of a running llama.cpp server with tools model in setting endpoint_tools. ");
}
return false;
}
else return true;
}
}

View file

@ -1,15 +0,0 @@
import * as assert from 'assert';
// You can import and use all API from the 'vscode' module
// as well as import your extension to test it
import * as vscode from 'vscode';
// import * as myExtension from '../../extension';
// suite('Extension Test Suite', () => {
// vscode.window.showInformationMessage('Start all tests.');
// test('Sample test', () => {
// assert.strictEqual(-1, [1, 2, 3].indexOf(5));
// assert.strictEqual(-1, [1, 2, 3].indexOf(0));
// });
// });

19
src/test/runTest.ts Normal file
View file

@ -0,0 +1,19 @@
import * as path from 'path';
import { runTests } from '@vscode/test-electron';
async function main() {
try {
const extensionDevelopmentPath = path.resolve(__dirname, '../../');
const extensionTestsPath = path.resolve(__dirname, './suite/index');
await runTests({
extensionDevelopmentPath,
extensionTestsPath,
});
} catch (err) {
console.error('Failed to run tests');
process.exit(1);
}
}
main();

59
src/test/suite/index.ts Normal file
View file

@ -0,0 +1,59 @@
import * as path from 'path';
import * as vscode from 'vscode';
import Mocha from 'mocha';
import * as fs from 'fs';
export async function run(): Promise<void> {
// Create the mocha test
const mocha = new Mocha({
ui: 'tdd',
color: true,
timeout: 10000,
globals: ['suite', 'test', 'setup', 'teardown', 'suiteSetup', 'suiteTeardown']
});
const testsRoot = path.resolve(__dirname, '..');
try {
// Find test files manually
const findTestFiles = (dir: string): string[] => {
const files: string[] = [];
const items = fs.readdirSync(dir);
for (const item of items) {
const fullPath = path.join(dir, item);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
files.push(...findTestFiles(fullPath));
} else if (item.endsWith('.test.js')) {
files.push(fullPath);
}
}
return files;
};
const files = findTestFiles(testsRoot);
// Add files to the test suite
files.forEach(f => mocha.addFile(f));
return new Promise((resolve, reject) => {
mocha.run(failures => {
if (failures > 0) {
reject(new Error(`${failures} tests failed.`));
} else {
resolve();
}
});
});
} catch (err) {
throw err;
}
}
// This function is called by VS Code when the test controller is created
export function activate(context: vscode.ExtensionContext): void {
console.log('Tests activated');
}

View file

@ -0,0 +1,184 @@
/// <reference types="mocha" />
import * as assert from 'assert';
import * as vscode from 'vscode';
import * as path from 'path';
import { Tools } from '../../tools';
import { Application } from '../../application';
import { Utils } from '../../utils';
import { suite, test, setup } from 'mocha';
// Mock the Application class for testing
class MockApplication {
configuration = {
MAX_CHARS_TOOL_RETURN: 10000
};
chatContext = {
getRagContextChunks: () => Promise.resolve([]),
getContextChunksInPlainText: () => Promise.resolve(''),
entries: []
};
llamaServer = {
executeCommandWithTerminalFeedback: () => Promise.resolve({ stdout: '', stderr: '' })
};
git = {
getLatestChanges: () => Promise.resolve('')
};
}
// Use Mocha's suite/test syntax
suite('Tools readFile Function Test Suite', () => {
let tools: Tools;
let mockApp: Application;
setup(() => {
mockApp = new MockApplication() as unknown as Application;
tools = new Tools(mockApp);
});
test('should read specific lines from a file', async () => {
console.log('Starting test: should read specific lines from a file');
// Mock the Utils.getAbsolutFilePath to return a valid path
const originalGetAbsolutFilePath = Utils.getAbsolutFilePath;
Utils.getAbsolutFilePath = (filePath: string) => {
console.log(`Mock getAbsolutFilePath called with: ${filePath}`);
if (filePath === 'src/menu.ts') {
const resolvedPath = path.resolve(__dirname, '../../../src/menu.ts');
console.log(`Resolved path: ${resolvedPath}`);
return resolvedPath;
}
return '';
};
try {
// Test reading lines 1-10 from menu.ts
const args = JSON.stringify({
file_path: 'src/menu.ts',
first_line: 1,
last_line_inclusive: 10
});
console.log(`Calling tools.readFile with args: ${args}`);
const result = await tools.readFile(args);
console.log(`Result length: ${result.length}`);
console.log(`Result preview: ${result.substring(0, 200)}...`);
// Verify that the result is not an error message
assert.ok(!result.includes('Error reading file'), `Expected file content but got error: ${result}`);
assert.ok(!result.includes('File not found'), `Expected file content but got file not found: ${result}`);
assert.ok(!result.includes('Invalid line range'), `Expected file content but got invalid line range: ${result}`);
// Verify that the result contains expected content
assert.ok(result.length > 0, 'Expected non-empty result');
// More detailed assertions
assert.ok(result.includes('import'), 'Expected to find import statements');
assert.ok(result.includes('export'), 'Expected to find export statements');
// The content should include the class definition
assert.ok(result.includes('class Menu') || result.includes('export class Menu'),
`Expected to find Menu class definition in the result. Actual content: ${result.substring(0, 500)}`);
} finally {
// Restore the original function
Utils.getAbsolutFilePath = originalGetAbsolutFilePath;
}
});
test('should return error for missing file path', async () => {
const args = JSON.stringify({
first_line: 1,
last_line_inclusive: 10
// file_path is missing
});
const result = await tools.readFile(args);
assert.strictEqual(result, 'The file is not provided.');
});
test('should return error for non-existent file', async () => {
const args = JSON.stringify({
file_path: 'non-existent-file.txt',
first_line: 1,
last_line_inclusive: 10
});
const result = await tools.readFile(args);
assert.ok(result.includes('File not found'), `Expected file not found error but got: ${result}`);
});
test('should return error for invalid line range', async () => {
// Mock to return a valid path
const originalGetAbsolutFilePath = Utils.getAbsolutFilePath;
Utils.getAbsolutFilePath = () => path.resolve(__dirname, '../../../src/menu.ts');
try {
const args = JSON.stringify({
file_path: 'src/menu.ts',
first_line: 1000, // Invalid line number
last_line_inclusive: 10 // Invalid range
});
const result = await tools.readFile(args);
assert.strictEqual(result, 'Invalid line range');
} finally {
Utils.getAbsolutFilePath = originalGetAbsolutFilePath;
}
});
test('should handle read entire file', async () => {
// Mock the Utils.getAbsolutFilePath to return a valid path
const originalGetAbsolutFilePath = Utils.getAbsolutFilePath;
Utils.getAbsolutFilePath = (filePath: string) => {
if (filePath === 'src/menu.ts') {
return path.resolve(__dirname, '../../../src/menu.ts');
}
return '';
};
try {
const args = JSON.stringify({
file_path: 'src/menu.ts',
should_read_entire_file: true
});
const result = await tools.readFile(args);
// Verify that the result is the entire file content
assert.ok(result.length > 0, 'Expected non-empty result for entire file');
assert.ok(result.includes('class Menu'), 'Expected to find class definition in entire file');
} finally {
Utils.getAbsolutFilePath = originalGetAbsolutFilePath;
}
});
test('debug: test individual components', async () => {
console.log('=== DEBUG TEST ===');
// Test 1: Check if Utils.getAbsolutFilePath works
const testPath = Utils.getAbsolutFilePath('src/menu.ts');
console.log(`Utils.getAbsolutFilePath result: ${testPath}`);
// Test 2: Check if file exists
const fs = require('fs');
const fileExists = fs.existsSync(testPath);
console.log(`File exists: ${fileExists}`);
// Test 3: Check file content
if (fileExists) {
const fileContent = fs.readFileSync(testPath, 'utf8');
console.log(`File content length: ${fileContent.length}`);
console.log(`First 100 chars: ${fileContent.substring(0, 100)}`);
}
// Test 4: Check mock application
console.log(`Mock app configuration:`, mockApp.configuration);
console.log(`Mock app chatContext:`, mockApp.chatContext);
assert.ok(true, 'Debug test completed');
});
});

View file

@ -29,8 +29,8 @@ export class TextEditor {
async showEditPrompt(editor: vscode.TextEditor) {
let chatUrl = this.app.configuration.endpoint_chat
if (!chatUrl) chatUrl = this.app.configuration.endpoint_tools;
let chatModel = this.app.menu.getChatModel();
if (!this.app.menu.isChatModelSelected()) chatModel = this.app.menu.getToolsModel();
let chatModel = this.app.getChatModel();
if (!this.app.isChatModelSelected()) chatModel = this.app.getToolsModel();
if (chatModel.endpoint) {
const chatEndpoint = Utils.trimTrailingSlash(chatModel.endpoint)
chatUrl = chatEndpoint ? chatEndpoint + "/" : "";
@ -38,7 +38,7 @@ export class TextEditor {
if (!chatUrl) {
const shouldSelectModel = await Utils.showUserChoiceDialog("Select a chat or tools model or an env with chat or tools model to edit code with AI.","Select")
if (shouldSelectModel){
this.app.menu.showEnvView();
this.app.llamaWebviewProvider.showEnvView();
vscode.window.showInformationMessage("After the chat model is loaded, try again using Edit with AI.")
return;
} else {

View file

@ -102,21 +102,48 @@ export class Tools {
uri = vscode.Uri.file(absolutePath);
const document = await vscode.workspace.openTextDocument(uri)
if (params.should_read_entire_file) return document.getText()
if (params.last_line_inclusive > document.lineCount) params.last_line_inclusive = document.lineCount
if (params.first_line < 0 || params.first_line > params.last_line_inclusive) {
// Validate required parameters
if (params.first_line === undefined || params.last_line_inclusive === undefined) {
return "first_line and last_line_inclusive parameters are required when should_read_entire_file is false";
}
// Validate parameter types
if (typeof params.first_line !== 'number' || typeof params.last_line_inclusive !== 'number') {
return "first_line and last_line_inclusive must be numbers";
}
// Convert 1-based line numbers to 0-based
let lastLine = params.last_line_inclusive - 1
let firstLine = params.first_line - 1
// Validate line numbers are positive
if (params.first_line < 1 || params.last_line_inclusive < 1) {
return "Line numbers must be positive integers starting from 1";
}
// Clamp to valid document range
if (firstLine < 0) firstLine = 0
if (lastLine >= document.lineCount) lastLine = document.lineCount-1
// Validate line range
if (firstLine > lastLine || firstLine > document.lineCount - 1) {
return 'Invalid line range';
}
let lastLine = Math.min(params.last_line_inclusive - 1, params.first_line + 249, document.lineCount -1)
// Apply 250-line limit using the converted 0-based firstLine
lastLine = Math.min(lastLine, firstLine + 249)
// Create range from first line's start to last line's end
const startPos = new vscode.Position(Math.max(params.first_line -1, 0), 0);
const startPos = new vscode.Position(Math.max(firstLine, 0), 0);
const endPos = new vscode.Position(lastLine, document.lineAt(lastLine).text.length);
const range = new vscode.Range(startPos, endPos);
return document.getText(range);
} catch (error) {
return "File not found: " + filePath;
console.error('Error reading file '+ filePath + ": " + error);
if (error instanceof Error) return "Error reading file: " + filePath + ": " + error // error.message;
else return "Error reading file: " + filePath
}
}
@ -236,8 +263,8 @@ export class Tools {
}
if (!yesApply) return Utils.MSG_NO_UESR_PERMISSION;
}
await Utils.applyEdits(changes)
return "The file is updated ";
let resultEdit = await Utils.applyEdits(changes)
return resultEdit;
} catch (error) {
console.error('Error changes since last commit:', error);
throw error;

View file

@ -180,4 +180,13 @@ export const translations: string[][] = [
["Delete agent command...", "Изтриване на команда на агент...", "Agentenbefehl löschen...", "Удалить команду агента...", "Eliminar comando de agente...", "删除代理命令...", "Supprimer la commande de l'agent..."],
["Export agent command...", "Експортиране на команда на агент...", "Agentenbefehl exportieren...", "Экспорт команды агента...", "Exportar comando de agente...", "导出代理命令...", "Exporter la commande de l'agent..."],
["Import agent command...", "Импортиране на команда на агент...", "Agentenbefehl importieren...", "Импорт команды агента...", "Importar comando de agente...", "导入代理命令...", "Importer la commande de l'agent..."],
["Invalid agent command", "Невалидна команда за агент", "Ungültiger Agentenbefehl", "Неверная команда агента", "Comando de agente no válido", "无效的代理命令", "Commande d'agent non valide"],
["Selected", "Избрано", "Ausgewählt", "Выбрано", "Seleccionado", "已选择", "Sélectionné"],
["Add", "Добави", "Hinzufügen", "Добавить", "Añadir", "添加", "Ajouter"],
["Delete", "Изтрий", "Löschen", "Удалить", "Eliminar", "删除", "Supprimer"],
["View", "Преглед", "Anzeigen", "Просмотреть", "Ver", "查看", "Voir"],
["Export", "Експортирай", "Exportieren", "Экспортировать", "Exportar", "导出", "Exporter"],
["Import", "Импортирай", "Importieren", "Импортировать", "Importar", "导入", "Importer"],
["Enter agent command name", "Въведете име на команда на агент", "Agentenbefehlsnamen eingeben", "Введите имя команды агента", "Ingresar nombre de comando de agente", "输入代理命令名称", "Entrer le nom de la commande de l'agent"],
["RAG", "RAG", "RAG", "RAG", "RAG", "RAG", "RAG"],
];

View file

@ -1,4 +1,4 @@
import vscode, { Uri } from "vscode";
import vscode, { QuickPickItem, Uri } from "vscode";
import { exec } from 'child_process';
import fs from 'fs';
import path from 'path';
@ -543,8 +543,9 @@ export class Utils {
return currentContent;
}
static applyEdits = async (diffText: string) => {
static applyEdits = async (diffText: string): Promise<string> => {
// Extract edit blocks from the diff-fenced format
let ret = "The file is updated";
let editBlocks: string[][] = [];
if (!diffText) return "Edit file: The input parameter is missing!";
const blocks = diffText.split("```diff")
@ -554,7 +555,7 @@ export class Utils {
if (editBlocks.length === 0) {
if (diffText.length > 0) editBlocks.push(Utils.extractConflictParts("```diff\n" + diffText))
else return "";
else return "Edit file: The input parameter is missing or incorrect format!";
}
for (const block of editBlocks) {
@ -587,17 +588,21 @@ export class Utils {
// Handle empty search text case
if (searchText.trim() === '') {
result += '\n' + replaceText;
await fs.promises.writeFile(absolutePath, result);
} else if (result.includes(searchText)) {
result = result.split(searchText).join(replaceText);
}
await fs.promises.writeFile(absolutePath, result);
await fs.promises.writeFile(absolutePath, result);
} else {
ret = "Error edititing file " + filePath + " - " + "The search text is not found in the file.";
}
} catch (error) {
if (error instanceof Error) return "Error edititing file " + filePath + " - " + error.message;
else return "Error edititing file " + filePath + " - " + error;
if (error instanceof Error) ret = "Error edititing file " + filePath + " - " + error.message;
else ret = "Error edititing file " + filePath + " - " + error;
}
}
}
}
return ret;
}
static extractConflictParts = (input: string): [string, string, string] => {
@ -847,4 +852,17 @@ export class Utils {
vscode.window.showErrorMessage(`Maximum attempts (${maxAttempts}) reached. Input validation failed.`);
return undefined;
}
static getStandardQpList(list:any[], prefix: string, lastModelNumber: number = 0) {
const items: QuickPickItem[] = [];
let i = lastModelNumber;
for (let elem of list) {
i++;
items.push({
label: i + ". " + prefix + elem.name,
description: elem.description,
});
}
return items;
}
}

View file

@ -216,6 +216,11 @@ const AgentView: React.FC<AgentViewProps> = ({
});
setInputText('');
setCurrentState('AI is working...');
} else {
vscode.postMessage({
command: 'addContextProjectFile',
fileLongName: fileLongName
});
}
if (textareaRef.current) {