mirror of
https://github.com/ggml-org/llama.vscode.git
synced 2026-05-07 01:15:23 +00:00
Compare commits
4 commits
master
...
agent_next
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
811e76bfd8 | ||
|
|
b81e164dd8 | ||
|
|
8e3b6b2de7 | ||
|
|
bb931008da |
50 changed files with 2243 additions and 8632 deletions
15
.vscode/launch.json
vendored
15
.vscode/launch.json
vendored
|
|
@ -18,21 +18,6 @@
|
|||
// "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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
|
|
@ -7,9 +7,5 @@
|
|||
"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",
|
||||
"testing.automaticallyOpenPeekView": "onFailure",
|
||||
"testing.automaticallyOpenTestResults": "onFailure",
|
||||
"mochaExplorer.files": "dist/test/suite/**/*.test.js",
|
||||
"mochaExplorer.require": "dist/test/runTest.js"
|
||||
"typescript.tsc.autoDetect": "off"
|
||||
}
|
||||
|
|
|
|||
103
.vscodeignore
103
.vscodeignore
|
|
@ -1,98 +1,15 @@
|
|||
# Exclude development sources and tests
|
||||
src/**
|
||||
ui/src/**
|
||||
ui/node_modules/**
|
||||
ui/package.json
|
||||
ui/package-lock.json
|
||||
src/test/**
|
||||
dist/test/**
|
||||
**/*.test.*
|
||||
**/*.spec.*
|
||||
|
||||
# Exclude maps and TypeScript sources from package
|
||||
**/*.map
|
||||
**/*.ts
|
||||
**/*.tsx
|
||||
|
||||
# Exclude build configs and project metadata not needed at runtime
|
||||
.eslintrc*
|
||||
.eslint*
|
||||
.prettier*
|
||||
*.code-workspace
|
||||
.vscode/**
|
||||
.idea/**
|
||||
.vscode-test/**
|
||||
.git/**
|
||||
src/**
|
||||
.gitignore
|
||||
.gitmodules
|
||||
.npmrc
|
||||
.github/**
|
||||
.nyc_output/**
|
||||
coverage/**
|
||||
|
||||
# Exclude local artifacts
|
||||
*.vsix
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Keep only runtime bundles and assets
|
||||
!dist/**
|
||||
!ui/dist/**
|
||||
!resources/**
|
||||
!llama.png
|
||||
!package.json
|
||||
!README.md
|
||||
!LICENSE
|
||||
|
||||
# Exclude production dependencies (we bundle the extension code)
|
||||
node_modules/**
|
||||
# Exclude VCS and workspace config
|
||||
.git/**
|
||||
.github/**
|
||||
.vscode/**
|
||||
|
||||
# Do not ship TypeScript sources or source maps
|
||||
src/**
|
||||
.yarnrc
|
||||
vsc-extension-quickstart.md
|
||||
**/tsconfig.json
|
||||
**/eslint.config.mjs
|
||||
**/*.map
|
||||
**/*.ts
|
||||
**/*.tsx
|
||||
**/*.test.*
|
||||
**/*.spec.*
|
||||
|
||||
# VS Code testing/coverage artifacts
|
||||
.vscode-test/**
|
||||
coverage/**
|
||||
|
||||
# Build artifacts and local packages
|
||||
*.vsix
|
||||
.vsix/**
|
||||
|
||||
# UI workspace: keep only built assets
|
||||
ui/**
|
||||
!ui/dist/**
|
||||
|
||||
# Keep built extension output and resources
|
||||
!dist/**
|
||||
!resources/**
|
||||
!llama.png
|
||||
!README.md
|
||||
!LICENSE
|
||||
!package.json
|
||||
|
||||
# Node modules: keep runtime code but drop heavy non-runtime extras
|
||||
node_modules/**/test/**
|
||||
node_modules/**/tests/**
|
||||
node_modules/**/__tests__/**
|
||||
node_modules/**/benchmark/**
|
||||
node_modules/**/benchmarks/**
|
||||
node_modules/**/example/**
|
||||
node_modules/**/examples/**
|
||||
node_modules/**/doc/**
|
||||
node_modules/**/docs/**
|
||||
node_modules/**/man/**
|
||||
node_modules/**/.github/**
|
||||
node_modules/**/.vscode/**
|
||||
|
||||
# Logs and misc
|
||||
npm-debug.log*
|
||||
yarn-error.log*
|
||||
**/.vscode-test.*
|
||||
ui/node_modules/**
|
||||
ui/src/**
|
||||
**/test
|
||||
|
|
|
|||
|
|
@ -68,12 +68,6 @@ Either use the [latest binaries](https://github.com/ggerganov/llama.cpp/releases
|
|||
|
||||
Here are recommended settings, depending on the amount of VRAM that you have:
|
||||
|
||||
- More than 64GB VRAM:
|
||||
|
||||
```bash
|
||||
llama-server --fim-qwen-30b-default
|
||||
```
|
||||
|
||||
- More than 16GB VRAM:
|
||||
|
||||
```bash
|
||||
|
|
|
|||
2652
package-lock.json
generated
2652
package-lock.json
generated
File diff suppressed because it is too large
Load diff
415
package.json
415
package.json
|
|
@ -2,11 +2,11 @@
|
|||
"name": "llama-vscode",
|
||||
"displayName": "llama-vscode",
|
||||
"description": "Local LLM-assisted text completion using llama.cpp",
|
||||
"version": "0.0.47",
|
||||
"version": "0.0.27",
|
||||
"publisher": "ggml-org",
|
||||
"repository": "https://github.com/ggml-org/llama.vscode",
|
||||
"engines": {
|
||||
"vscode": "^1.109.0"
|
||||
"vscode": "^1.100.0"
|
||||
},
|
||||
"icon": "llama.png",
|
||||
"activationEvents": [
|
||||
|
|
@ -17,13 +17,6 @@
|
|||
],
|
||||
"main": "./dist/extension.js",
|
||||
"contributes": {
|
||||
"languageModelChatProviders": [
|
||||
{
|
||||
"vendor": "llama-vscode",
|
||||
"displayName": "llama.vscode",
|
||||
"managementCommand": "extension.showMenu"
|
||||
}
|
||||
],
|
||||
"viewsContainers": {
|
||||
"activitybar": [
|
||||
{
|
||||
|
|
@ -82,12 +75,12 @@
|
|||
"title": "llama-vscode: Ask AI"
|
||||
},
|
||||
{
|
||||
"command": "extension.editSelectedText",
|
||||
"title": "llama-vscode: Edit Selected Text with AI"
|
||||
"command": "extension.askAiWithContext",
|
||||
"title": "llama-vscode: Ask AI With Context"
|
||||
},
|
||||
{
|
||||
"command": "extension.editAllSearchFiles",
|
||||
"title": "llama-vscode: Edit All Search Files"
|
||||
"command": "extension.editSelectedText",
|
||||
"title": "llama-vscode: Edit Selected Text with AI"
|
||||
},
|
||||
{
|
||||
"command": "extension.acceptTextEdit",
|
||||
|
|
@ -154,16 +147,6 @@
|
|||
"key": "ctrl+x",
|
||||
"when": "editorTextFocus"
|
||||
},
|
||||
{
|
||||
"command": "extension.selectNextSuggestion",
|
||||
"key": "alt+]",
|
||||
"when": "editorTextFocus && inlineSuggestionVisible"
|
||||
},
|
||||
{
|
||||
"command": "extension.selectPreviousSuggestion",
|
||||
"key": "alt+[",
|
||||
"when": "editorTextFocus && inlineSuggestionVisible"
|
||||
},
|
||||
{
|
||||
"command": "extension.acceptFirstLine",
|
||||
"key": "shift+tab",
|
||||
|
|
@ -189,6 +172,11 @@
|
|||
"key": "ctrl+;",
|
||||
"when": "editorTextFocus"
|
||||
},
|
||||
{
|
||||
"command": "extension.askAiWithContext",
|
||||
"key": "ctrl+Shift+;",
|
||||
"when": "editorTextFocus"
|
||||
},
|
||||
{
|
||||
"command": "extension.askAiWithTools",
|
||||
"key": "ctrl+Shift+t",
|
||||
|
|
@ -279,11 +267,6 @@
|
|||
"default": "",
|
||||
"description": "The URL to be used by the extension for creating embeddings."
|
||||
},
|
||||
"llama-vscode.max_parallel_completions": {
|
||||
"type": "number",
|
||||
"default": 3,
|
||||
"description": "The max number of parallel completions. Switching between completions could be done with Alt+] (next) or Alt =+[ (previous). "
|
||||
},
|
||||
"llama-vscode.new_completion_model_port": {
|
||||
"type": "number",
|
||||
"default": 8012,
|
||||
|
|
@ -345,11 +328,7 @@
|
|||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "Description of the agent - for what purposes should be used, what are his strengths, etc."
|
||||
},
|
||||
"subagentEnabled": {
|
||||
"type": "string",
|
||||
"description": "If the agent could be used as subagent of another agent to execute a specific task."
|
||||
"description": "Description of the model - for what purposes should be used, what are his strengths, etc."
|
||||
},
|
||||
"systemInstruction": {
|
||||
"type": "array",
|
||||
|
|
@ -359,38 +338,6 @@
|
|||
"description": "The system instructions for this agent",
|
||||
"default": ""
|
||||
},
|
||||
"toolsModel": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Name for this model to be shown to the user"
|
||||
},
|
||||
"endpoint": {
|
||||
"type": "string",
|
||||
"description": "The endpoint, from where to access the model",
|
||||
"default": ""
|
||||
},
|
||||
"aiModel": {
|
||||
"type": "string",
|
||||
"description": "The name of the AI model as expected by the provider",
|
||||
"default": ""
|
||||
},
|
||||
"isKeyRequired": {
|
||||
"type": "boolean",
|
||||
"description": "Is key requried for the endpoint",
|
||||
"default": false
|
||||
},
|
||||
"localStartCommand": {
|
||||
"type": "string",
|
||||
"description": "Command to be used for sterting the model locally.",
|
||||
"default": ""
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
]
|
||||
},
|
||||
"tools": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
|
@ -406,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"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -491,211 +438,7 @@
|
|||
"delete_file",
|
||||
"get_diff",
|
||||
"edit_file",
|
||||
"ask_user",
|
||||
"update_todo_list",
|
||||
"delegate_task"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Unite test writer",
|
||||
"description": "Writes the unit tests. The input should provide a path to a source file to be tested.",
|
||||
"systemInstruction": [
|
||||
"You are an expert software engineer specializing in writing unit tests. Your task is to generate high‑quality, reliable, and maintainable unit tests based on the user’s instructions and the provided source code. You must infer the programming language, testing framework, and project conventions from the source file and any accompanying context (such as imports, file extensions, or existing test files).",
|
||||
"Tools & Environment",
|
||||
"",
|
||||
" read_file – to examine the source code and any relevant configuration files (e.g., package.json, pom.xml, requirements.txt, Cargo.toml, etc.).",
|
||||
"",
|
||||
" edit_file – to create or modify test files.",
|
||||
"",
|
||||
" run_terminal_command – to execute tests and report results.",
|
||||
"",
|
||||
"Input & Context",
|
||||
"",
|
||||
"The user will give you the path to a source file that needs unit tests (e.g., src/services/user_service.py, lib/user.dart, internal/user.go). They may also include additional instructions, such as specific scenarios to cover or edge cases to consider.",
|
||||
"Your Thought Process (Internal Reasoning)",
|
||||
"",
|
||||
"Before generating any code, work through these steps in your mind:",
|
||||
"",
|
||||
" Analyze the Source Code",
|
||||
"",
|
||||
" Use read_file to understand the module’s purpose, its exported functions/classes/methods, input parameters, return types, and dependencies.",
|
||||
"",
|
||||
" Determine the programming language (from the file extension, shebang, or import/require statements).",
|
||||
"",
|
||||
" Identify all public APIs that need testing.",
|
||||
"",
|
||||
" Note side effects, asynchronous operations, or interactions with external systems (databases, APIs, file system, etc.).",
|
||||
"",
|
||||
" Infer the Testing Conventions",
|
||||
"",
|
||||
" Look for an existing test directory (e.g., test/, tests/, spec/, __tests__/) and the naming pattern of existing test files (e.g., *.test.js, *_test.py, *_spec.rb).",
|
||||
"",
|
||||
" Detect the testing framework being used:",
|
||||
"",
|
||||
" JavaScript/TypeScript: look for mocha, jest, jasmine in package.json.",
|
||||
"",
|
||||
" Python: look for pytest, unittest in imports or config files.",
|
||||
"",
|
||||
" Java: look for JUnit in pom.xml or build.gradle.",
|
||||
"",
|
||||
" Go: look for testing package imports, etc.",
|
||||
"",
|
||||
" Determine the preferred assertion style (e.g., assert module, expect, should, assertThat).",
|
||||
"",
|
||||
" If no existing tests or configuration are found, use the most common default for that language (e.g., pytest for Python, JUnit 5 for Java, go test for Go, Mocha + assert for Node.js).",
|
||||
"",
|
||||
" Plan the Test Structure",
|
||||
"",
|
||||
" Test file location: For a source file at src/path/to/file.ext, the test file should normally be placed at test/path/to/file_test.ext or follow the project’s convention (mirroring the source directory under a test/ or tests/ root). Ensure the directory structure is created if needed.",
|
||||
"",
|
||||
" Plan the outer test suite (e.g., describe('moduleName', ...) in Mocha, a test class in JUnit, or a module‑level docstring in pytest).",
|
||||
"",
|
||||
" Plan nested suites for each function or method.",
|
||||
"",
|
||||
" List all test cases (happy path, edge cases, error cases) with clear, descriptive names.",
|
||||
"",
|
||||
" Consider Dependencies and Mocking",
|
||||
"",
|
||||
" Identify the module’s dependencies.",
|
||||
"",
|
||||
" Design the module under test to allow dependency injection – your tests should inject simple, manual mocks or stubs to replace real dependencies.",
|
||||
"",
|
||||
" Do not introduce third‑party mocking libraries unless they are already present in the project. Rely on manual mocks (e.g., creating test doubles yourself).",
|
||||
"",
|
||||
" Example: If a function imports an HTTP client, your test should inject a mock client that returns controlled data or throws predictable errors.",
|
||||
"",
|
||||
"Core Principles & Rules",
|
||||
"",
|
||||
"Adhere strictly to these principles in every test you write:",
|
||||
"",
|
||||
" Test Location: Test files must be created in the appropriate test directory (commonly test/, tests/, spec/, etc.) mirroring the source structure. Use the naming convention inferred from the project.",
|
||||
"",
|
||||
" Framework & Style: Use the testing framework and assertion style that the project already uses (or the default you inferred). Write idiomatic tests for that language.",
|
||||
"",
|
||||
" Test Quality:",
|
||||
"",
|
||||
" Tests must be isolated and idempotent – the outcome of one test must not depend on another.",
|
||||
"",
|
||||
" Each test should verify one specific behavior.",
|
||||
"",
|
||||
" Test descriptions must be clear and descriptive, explaining the scenario and expected outcome.",
|
||||
"",
|
||||
" Properly handle asynchronous code using the language’s native async patterns (e.g., async/await, Future, Promise). Ensure the test framework waits for completion.",
|
||||
"",
|
||||
" Reset any module state or mocks in setup/teardown hooks (e.g., beforeEach, setUp, @BeforeEach) to guarantee tests can run in any order.",
|
||||
"",
|
||||
" Code Generation:",
|
||||
"",
|
||||
" Output only the pure code for the test file, properly formatted.",
|
||||
"",
|
||||
" Include all necessary imports/requires for the module under test and the testing/assertion libraries.",
|
||||
"",
|
||||
" Import the actual functions/classes from the source file. Mocking is done inside the test, not by mocking the import itself.",
|
||||
"",
|
||||
" No Source Modification: You cannot modify the source code. If the source is untestable due to poor design (e.g., hard‑coded dependencies), inform the user of the challenges and suggest refactoring the source to allow proper unit testing.",
|
||||
"",
|
||||
"Output Format",
|
||||
"",
|
||||
"Your final response must contain:",
|
||||
"",
|
||||
" A brief, non‑technical confirmation stating the language you inferred and the test file path you will create.",
|
||||
"",
|
||||
"Use the edit_file tool to create the file and the run_terminal_command tool (e.g., npx mocha 'test/services/userService.spec.ts') to verify your work, reporting the results back to the user.",
|
||||
"",
|
||||
"Crucially, you cannot modify the source code itself. If the source code is not testable due to poor design (e.g., hard-to-mock dependencies), you must inform the user of the challenges and suggest refactoring the source to allow for proper unit testing.",
|
||||
""
|
||||
],
|
||||
"tools": [
|
||||
"run_terminal_command",
|
||||
"search_source",
|
||||
"read_file",
|
||||
"list_directory",
|
||||
"regex_search",
|
||||
"delete_file",
|
||||
"edit_file",
|
||||
"update_todo_list"
|
||||
],
|
||||
"subagentEnabled": true
|
||||
},
|
||||
{
|
||||
"name": "Agent creator",
|
||||
"description": "Creates new agent. Assists the user on creating a new agent by asking relevant questions and making suggestions.",
|
||||
"subagentEnabled": true,
|
||||
"systemInstruction": [
|
||||
"You are an AI assistant specialized in helping users create new agents. Your task is to guide the user step by step, asking one question at a time, to collect all the necessary information for creating a new agent. Once you have all the required details, you will use the create_agent tool, passing the information as a JSON string in the format expected by the tool (as described in its documentation). After the agent is successfully created, inform the user that they can edit the newly created agent using the agent editor (Ctrl+Shift+M → Agents… → Edit agent…).",
|
||||
"",
|
||||
"Required Information:",
|
||||
"",
|
||||
" name (string): The name of the new agent.",
|
||||
"",
|
||||
" description (string): A brief description of what the agent does.",
|
||||
"",
|
||||
" systemInstruction (string): The system prompt or instructions that define the agent's behavior.",
|
||||
"",
|
||||
"Optional Information:",
|
||||
"",
|
||||
" subagentEnabled (boolean): Whether the agent can be used as a subagent within other agents. Ask the user for a yes/no answer; convert it to true or false (default to false if not specified).",
|
||||
"",
|
||||
" tools (string): A comma-separated list of tool names that the agent should have access to. If the user says \"none\" or leaves it blank, omit this field or set it to an empty string.",
|
||||
"",
|
||||
"Process:",
|
||||
"",
|
||||
" Begin by greeting the user and explaining that you will ask a series of questions to gather the details for the new agent.",
|
||||
"",
|
||||
" Ask for the name first. Wait for the user's response.",
|
||||
"",
|
||||
" After receiving the name, ask for the description.",
|
||||
"",
|
||||
" Then ask for the systemInstruction.",
|
||||
"",
|
||||
" Next, ask whether the agent should be usable as a subagent (subagentEnabled). Prompt for a yes/no answer. If the answer is ambiguous, ask for clarification.",
|
||||
"",
|
||||
" Finally, ask for any tools the agent should have. Prompt for a comma-separated list or indicate that they can say \"none\".",
|
||||
"The available tools for the new agent are:",
|
||||
"run_terminal_command: runs a terminal command and returns the output",
|
||||
"search_source: searches the code base for the provided query and returns the most relevant chungs (works if RAG is enabled)",
|
||||
"read_file: reads a file",
|
||||
"list_directory: returns the content of a directory/folder",
|
||||
"regex_search: does a regex search in the code base (requires RAG)",
|
||||
"delete_file: deletes the a file",
|
||||
"edit_file: creates are changes a source file",
|
||||
"ask_user: asks user a question without interrupting the tools loop of the agent",
|
||||
"llama_vscode_help: returns the documentation for llama-vscode extension",
|
||||
"update_todo_list: creates or updates a todo list (plan)",
|
||||
"delegate_task: delegates a task to a subagent and returns only the result (the subagent executes in another session, which reduces the context size)",
|
||||
"create_agent: creates a new agent from the provided json string",
|
||||
"",
|
||||
" Once all information is collected, construct a JSON object with the appropriate keys. Ensure that boolean values are represented as true or false (without quotes) and that the tools string is included only if provided.",
|
||||
"",
|
||||
" Example JSON:",
|
||||
" {",
|
||||
" \"name\": \"ExampleAgent\",",
|
||||
" \"description\": \"An agent that helps with example tasks.\",",
|
||||
" \"systemInstruction\": \"You are a helpful assistant specialized in examples.\",",
|
||||
" \"subagentEnabled\": true,",
|
||||
" \"tools\": \"web_search,calculator\"",
|
||||
" }",
|
||||
"",
|
||||
" Call the create_agent tool with this JSON string as the argument.",
|
||||
"",
|
||||
" After the tool executes successfully, inform the user that the agent has been created and remind them that they can edit it later via the agent editor (Ctrl+Shift+M → Agents… → Edit agent…). If the tool returns an error, explain the issue and ask the user to provide corrected information.",
|
||||
"",
|
||||
"Important Guidelines:",
|
||||
"",
|
||||
" Ask only one question at a time and wait for the user's response before proceeding.",
|
||||
"",
|
||||
" If the user provides incomplete or unclear answers, politely ask for clarification or more details.",
|
||||
"",
|
||||
" Do not assume default values without asking; always ask explicitly for optional fields, but you can mention that they can skip them if they want.",
|
||||
"",
|
||||
" Keep your tone friendly and helpful. Make the process feel like a guided conversation.",
|
||||
"",
|
||||
" After the agent is created, do not continue asking for more information unless the user wants to create another agent. If they do, you may restart the process.",
|
||||
"",
|
||||
""
|
||||
],
|
||||
"tools": [
|
||||
"create_agent"
|
||||
"ask_user"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
|
@ -737,14 +480,15 @@
|
|||
]
|
||||
},
|
||||
"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",
|
||||
|
|
@ -752,7 +496,8 @@
|
|||
"prompt": [
|
||||
"Explain the provided source code."
|
||||
],
|
||||
"context": []
|
||||
"context": [
|
||||
]
|
||||
}
|
||||
],
|
||||
"description": "The list of agent commands, which could be selected by the user"
|
||||
|
|
@ -873,11 +618,6 @@
|
|||
"localStartCommand": "llama-server -hf ggml-org/Qwen2.5-Coder-7B-Instruct-Q8_0-GGUF -ngl 99 -ub 1024 -b 1024 --ctx-size 0 --cache-reuse 256 -np 2 --port 8011",
|
||||
"endpoint": "http://127.0.0.1:8011"
|
||||
},
|
||||
{
|
||||
"name": "Qwen2.5-Coder-14B-Instruct-Q8_0-GGUF (> 32GB VRAM)",
|
||||
"localStartCommand": "llama-server -hf ggml-org/Qwen2.5-Coder-14B-Instruct-Q8_0-GGUF -ngl 99 -ub 1024 -b 1024 --ctx-size 0 --cache-reuse 256 -np 2 --port 8011",
|
||||
"endpoint": "http://127.0.0.1:8011"
|
||||
},
|
||||
{
|
||||
"name": "Qwen2.5-Coder-1.5B-Instruct-Q8_0-GGUF (CPU Only)",
|
||||
"localStartCommand": "llama-server -hf ggml-org/Qwen2.5-Coder-1.5B-Instruct-Q8_0-GGUF -ub 1024 -b 1024 -dt 0.1 --ctx-size 0 --cache-reuse 256 -np 2 --port 8011",
|
||||
|
|
@ -1247,7 +987,7 @@
|
|||
"ragEnabled": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
},
|
||||
"envStartLastUsed": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
|
|
@ -1754,11 +1494,6 @@
|
|||
"default": true,
|
||||
"description": "If code completion should be triggered automatically (true) or only by pressing Ctrl+l."
|
||||
},
|
||||
"llama-vscode.debounce_ms": {
|
||||
"type": "number",
|
||||
"default": 0,
|
||||
"description": "Milliseconds to wait after the last keystroke before sending a completion request (0 = disabled). Useful on low-end hardware to avoid triggering inference on every keystroke."
|
||||
},
|
||||
"llama-vscode.api_key": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
|
|
@ -1784,31 +1519,6 @@
|
|||
"default": "",
|
||||
"description": "self-signed certificate file - path/to/cert.pem"
|
||||
},
|
||||
"llama-vscode.health_check_interval_s": {
|
||||
"type": "number",
|
||||
"default": 30,
|
||||
"description": "Models health check interval in seconds"
|
||||
},
|
||||
"llama-vscode.health_check_compl_enabled": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Works only for llama.cpp servers - enables health check for completion model"
|
||||
},
|
||||
"llama-vscode.health_check_chat_enabled": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Works only for llama.cpp servers - enables health check for chat model"
|
||||
},
|
||||
"llama-vscode.health_check_embs_enabled": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Works only for llama.cpp servers - enables health check for embeddings model"
|
||||
},
|
||||
"llama-vscode.health_check_tools_enabled": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Works only for llama.cpp servers - enables health check for tools model"
|
||||
},
|
||||
"llama-vscode.n_prefix": {
|
||||
"default": 256,
|
||||
"type": "number",
|
||||
|
|
@ -1924,11 +1634,6 @@
|
|||
"default": true,
|
||||
"description": "Enable/disable tool run_terminal_command"
|
||||
},
|
||||
"llama-vscode.tool_create_agent_enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Enable/disable tool create_agent"
|
||||
},
|
||||
"llama-vscode.tools_custom": {
|
||||
"type": "array",
|
||||
"description": "Array of tool definitions for REST requests to LLM",
|
||||
|
|
@ -1958,9 +1663,7 @@
|
|||
"type": {
|
||||
"type": "string",
|
||||
"description": "Type of the tool",
|
||||
"enum": [
|
||||
"function"
|
||||
]
|
||||
"enum": ["function"]
|
||||
},
|
||||
"function": {
|
||||
"type": "object",
|
||||
|
|
@ -1981,9 +1684,7 @@
|
|||
"type": {
|
||||
"type": "string",
|
||||
"description": "Type of parameters object",
|
||||
"enum": [
|
||||
"object"
|
||||
]
|
||||
"enum": ["object"]
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
|
|
@ -1998,31 +1699,22 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"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",
|
||||
|
|
@ -2103,15 +1795,15 @@
|
|||
"default": false,
|
||||
"description": "Enable/disable tool llama-vscode_help"
|
||||
},
|
||||
"llama-vscode.tool_update_todo_list_enabled": {
|
||||
"llama-vscode.tool_save_plan_enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Enable/disable tool update_todo_list"
|
||||
"default": false,
|
||||
"description": "Enable/disable tool llama-vscode_help"
|
||||
},
|
||||
"llama-vscode.tool_delegate_task_enabled": {
|
||||
"llama-vscode.tool_update_task_enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Enable/disable tool delegate_task"
|
||||
"default": false,
|
||||
"description": "Enable/disable tool llama-vscode_help"
|
||||
},
|
||||
"llama-vscode.tool_custom_tool_description": {
|
||||
"type": "string",
|
||||
|
|
@ -2148,11 +1840,6 @@
|
|||
"default": 20,
|
||||
"description": "Max number of iterations with AI when working with tools. If you are working with paid AI providers, big number here could result in higher costs."
|
||||
},
|
||||
"llama-vscode.plan_review_frequency": {
|
||||
"type": "number",
|
||||
"default": 5,
|
||||
"description": "How often (interations count) the plan/todos should be sent to the LLM again during a session."
|
||||
},
|
||||
"llama-vscode.chats_max_history": {
|
||||
"type": "number",
|
||||
"default": 50,
|
||||
|
|
@ -2178,11 +1865,6 @@
|
|||
"default": false,
|
||||
"description": "Show the details about the tools calls in UI - arguments and results."
|
||||
},
|
||||
"llama-vscode.skills_folder": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "The folder, where are the skills are stored. If empty , <project_folder>/skills will be used."
|
||||
},
|
||||
"llama-vscode.language": {
|
||||
"type": "string",
|
||||
"default": "en",
|
||||
|
|
@ -2246,36 +1928,23 @@
|
|||
"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",
|
||||
"test": "node ./dist/test/runTest.js",
|
||||
"compile": "tsc -p ./",
|
||||
"bundle": "esbuild src/extension.ts --bundle --platform=node --format=cjs --external:vscode --outfile=dist/extension.js --minify",
|
||||
"vscode:prepublish": "npm run bundle",
|
||||
"lint": "eslint --ext .ts,.tsx .",
|
||||
"format": "prettier --write --ignore-path .gitignore '**/*'"
|
||||
"postinstall": "npm run build-ui"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.1.2",
|
||||
"globby": "^14.1.0",
|
||||
"ignore": "^7.0.4",
|
||||
"js-yaml": "^4.1.1",
|
||||
"openai": "^4.80.1",
|
||||
"picomatch": "^4.0.2",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"simple-git": "^3.28.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/types": "^7.28.4",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/micromatch": "^4.0.9",
|
||||
"@types/mocha": "^10.0.10",
|
||||
"@types/node": "^18.0.0",
|
||||
"@types/picomatch": "^4.0.0",
|
||||
"@types/vscode": "^1.109.0",
|
||||
"@vscode/test-cli": "^0.0.11",
|
||||
"@vscode/test-electron": "^2.5.2",
|
||||
"esbuild": "^0.27.0",
|
||||
"glob": "^11.0.3",
|
||||
"mocha": "^11.7.4",
|
||||
"@types/vscode": "^1.100.0",
|
||||
"typescript": "^4.8.0",
|
||||
"webpack": "^5.100.2",
|
||||
"webpack-cli": "^4.10.0"
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@ Example:
|
|||
1. Select several lines of source code
|
||||
2. Press Ctrl+Shift+A (or right click and select "llama-vscode: Show Llama Agent") - this will attach the selected lines to the prompt
|
||||
3. Inside the agent prompt press "/" and select "explain"
|
||||
The agent will explain the selected code.## Chat with AI about llama-vscode
|
||||
The agent will explain the selected code.
|
||||
|
||||
## Chat with AI about llama-vscode
|
||||
|
||||
### Requred servers
|
||||
- Tools server
|
||||
|
|
@ -23,6 +25,12 @@ This is a conversation with the llama-vscode help agent AI about llama-vscode, s
|
|||
- From llama-vscode menu select "Chat with AI about llama-vscode" -> the agent will be opened
|
||||
- Enter your question about llama-vscode
|
||||
The first time it could take longer to answer. The following questions will be answered faster as the help information will be cached.
|
||||
|
||||
|
||||
## Chat with AI with project context
|
||||
This is removed. Chat with AI with project context is equal to using agent with the tool search_source. The agent has many other tools and is therefore a better choice.
|
||||
|
||||
|
||||
## Chat with AI
|
||||
|
||||
### Requred servers
|
||||
|
|
@ -33,7 +41,9 @@ This is a conversation with the local AI. Mainly for asking questions for refere
|
|||
- Press Ctrl+; inside an editor (or select from llama.vscode menu Chat with AI) - A chat window will open inside VS Code
|
||||
- Enter your message and start the chat
|
||||
|
||||
## Code completion
|
||||

|
||||
|
||||
## Code completion
|
||||
|
||||
### Requred servers
|
||||
- Completion server
|
||||
|
|
@ -51,20 +61,8 @@ https://github.com/user-attachments/assets/97bb1418-dcea-4a49-8332-13b2ab4da661
|
|||
|
||||
|
||||
|
||||
## Copilot Chat Model Provider
|
||||
|
||||
### Overview
|
||||
Llama-vscode could be used as a VS Code copilot chat model provider. With other words llama-vscode could provide models for the copilot. The provided models could be from local models or openrouter.com or other appliation, which servers the tools models for llama-vscode. This way you could automatically download and start locally models by llama.cpp and llama-vscode and use them with Copilot for free.
|
||||
|
||||
### How to use it
|
||||
1. Select/Start tools model from llama-vscode (local or external)
|
||||
<img width="485" height="875" alt="copilotSelectToolsModel" src="https://github.com/user-attachments/assets/caa33531-22f4-46dd-b429-7498c45c93e9" />
|
||||
|
||||
2. In VS Code Copilot show the models list -> Other Models -> Manage Models
|
||||
<img width="1404" height="754" alt="CopilotManageModels" src="https://github.com/user-attachments/assets/dc861aa1-db86-46ff-83c1-98c7a435ad06" />
|
||||
|
||||
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
|
||||

|
||||
|
||||
## Custom eval tool
|
||||
|
||||
### Overview
|
||||
|
|
@ -87,6 +85,8 @@ https://github.com/user-attachments/assets/fb12d56f-61e8-409b-b888-0a524167e116
|
|||
https://github.com/user-attachments/assets/7e928fc3-da14-4834-a414-0f8e23593155
|
||||
|
||||
|
||||
|
||||
|
||||
## Custom tool
|
||||
|
||||
### Overview
|
||||
|
|
@ -105,6 +105,8 @@ https://github.com/user-attachments/assets/46602f8c-bd45-4794-9f5c-6ebe262c396a
|
|||
|
||||
https://github.com/user-attachments/assets/50baa8c3-f426-4901-a443-8882da644800
|
||||
|
||||
|
||||
|
||||
## Delete models
|
||||
|
||||
### Overview
|
||||
|
|
@ -120,44 +122,8 @@ You could delete the GGUF files from this folder. If they are missing, but are n
|
|||
|
||||
|
||||
|
||||
## Edit Agent
|
||||
|
||||
### Overview
|
||||
Edit agent view is used for adding and editing agents. From there it is also possible to delete and copy an existing agent as a new one. The identifier of an agent is it's name. For now there is no tools model as part of the agent (the currently selected tools model will be used)
|
||||
|
||||
<img width="582" height="977" alt="image" src="https://github.com/user-attachments/assets/9a406e7a-09ea-4f04-9054-f709bcdb038a" />
|
||||
|
||||
|
||||
### How to use it
|
||||
Edit agent view could be shown in one of the following ways:
|
||||
- In the left sidebar click llama-viscode button and after that on the upper part click button Show Edit Agent View (pencip image)
|
||||
- From llama-vscode menu (Ctrl+Shift+M) select Agents...-> Add agent (or Edit agent or Copy agent)
|
||||
- From environment view, when an agent is selected, click button Edit - this will show the selected agent in the Edit Agent View
|
||||
|
||||
Edit existing agent:
|
||||
1. Click Select button and load an agent to be edited.
|
||||
2. Change the Description and System Instructions fields (if needed)
|
||||
3. Click Add Tools button and select the tools to be used for by the agent.
|
||||
4. Click Save button
|
||||
|
||||
Add new agent:
|
||||
1. Click New button
|
||||
2. Enter Name, Description and System Instructions for the agent
|
||||
3. Click Add Tools button and select the tools to be used for by the agent.
|
||||
4. Click Save button
|
||||
|
||||
Copy existing agent as a new one:
|
||||
1. Click Copy as New button
|
||||
2. Edit Name, Description and System Instructions for the agent
|
||||
3. Click Add Tools button and select the tools to be used for by the agent.
|
||||
4. Click Save button
|
||||
|
||||
Delete agent:
|
||||
1. Click Delete button
|
||||
2. Select an agent to be deleted from the list
|
||||
3. Confirm the deletion of the agent
|
||||
|
||||
|
||||
|
||||
|
||||
## Edit with AI
|
||||
|
||||
### Requred servers
|
||||
|
|
@ -173,7 +139,9 @@ Delete agent:
|
|||
https://github.com/user-attachments/assets/887d0b88-717b-4765-b565-d4c54673bde8
|
||||
|
||||
|
||||
## Env
|
||||

|
||||
|
||||
## Env
|
||||
|
||||
### What is env
|
||||
Env (short for environment) is a group of models, agent and settings. Env makes it easier for the users to prepare the environment for their needs. Selecting an env with a given intent will make sure all needed servers are available. One env could contain up to 4 different models - for completions, chat, embeddings, tools. Env could also contain an agent and settings for enabling/disabling completions, rag and starting last selected env on startup. If the user wants to use only code completions functionality, he/she could select an env with only one model for completions. If the user wants to use all the functionality from llama-vscode, he/she could select an env with full package of models.
|
||||
|
|
@ -186,6 +154,8 @@ There is a page in llama-vscode UI with the current environment details. From th
|
|||
|
||||
<img width="540" height="996" alt="image" src="https://github.com/user-attachments/assets/b1a78d7a-8602-451a-b304-fc967fb66696" />
|
||||
|
||||
|
||||
|
||||
## Generate a commit message
|
||||
|
||||
### Requred servers
|
||||
|
|
@ -195,51 +165,14 @@ There is a page in llama-vscode UI with the current environment details. From th
|
|||
In the source control panel just click on the star button (near the commit button).
|
||||
This generate a commit message, based on the current changes.
|
||||
|
||||
## Health check
|
||||
|
||||
### Overview
|
||||
Health check for the models is added. It works with llama.cpp server or other servers, which supports endpoint/health REST service. When the health check is enabled, the current state of the selected model is visible in the environment view. The health check is done every 30 seconds (could be changed from setting Health_check_interval_s). It could be triggered also manually by the user by clicking the appropriate button in the environment view (after the selected model name).
|
||||
|
||||
### How to use it
|
||||
1. Enable health check in settings for the appropriate model (e.g. for completion Health_check_compl_enabled)
|
||||
2. Open environment view and select the completion model (for example)
|
||||
3. The health check will be monitored periodically and the status will be displayed in the environment view
|
||||
4. Optionally, the health check can be triggered manually by clicking the appropriate button
|
||||
|
||||
Settings:
|
||||
- Health_check_interval_s: The interval in seconds for the health check
|
||||
- Health_check_compl_enabled: Enable/disable health check for completion model
|
||||
- Health_check_chat_enabled: Enable/disable health check for chat model
|
||||
- Health_check_embs_enabled: Enable/disable health check for embedding model
|
||||
- Health_check_tools_enabled: Enable/disable health check for tools model
|
||||
|
||||
<img width="580" height="779" alt="image" src="https://github.com/user-attachments/assets/dca91333-687e-4856-b187-25df50d17b1c" />
|
||||
|
||||
<img width="580" height="779" alt="image" src="https://github.com/user-attachments/assets/bb29e0c8-85b4-4e7a-a3d9-f2d9a1679d3d" />
|
||||
|
||||
|
||||
## Version 0.0.45 is released (04.03.2026)
|
||||

|
||||
|
||||
## Version 0.0.27 is released (21.09.2025)
|
||||
## What is new
|
||||
|
||||
- Configurable debounce for inline completion requests - setting debounce_ms.
|
||||
llama-vscode will wait debounce_ms after a keystroke before sending a request to the LLM for inline code completion. If in the meantime there is another keystroke, the request for the previous keystroke is cancelled. Useful on low end hardware to avoid triggering code completion on every keystroke.
|
||||
|
||||
- Notification "Extension is updated" is shown only on version change, not on every setting change (as was before)
|
||||
|
||||
|
||||
## Version 0.0.44 is released (03.03.2026)
|
||||
## What is new
|
||||
|
||||
- Subagents implemented (with tool delegate_task) - now each agent, which has "Available as Subagent" checked could be used as a subagent
|
||||
|
||||
- new agent - Unit Test Writer
|
||||
|
||||
- new tool create_agent
|
||||
|
||||
- new agent "Agent creator"
|
||||
|
||||
- Files SOUL.md and USER.md (if available in the project root) will be added to the context
|
||||
|
||||
- xAI Grog4 free (from OpenRouter) added to the initial models
|
||||
- Chat with AI with project context removed (agent does it better)
|
||||
- Chat with AI about llama-vscode is now with agent, not using webui
|
||||
- Agent - new buttons "Tools Model" and "Agent" - possibility to view the selected model and agent and to change them.
|
||||
|
||||
## Setup instructions for llama.cpp server
|
||||
|
||||
|
|
@ -287,6 +220,8 @@ llama-vscode will wait debounce_ms after a keystroke before sending a request to
|
|||
|
||||
### [Model selection](https://github.com/ggml-org/llama.vscode/wiki/Model-selection)
|
||||
|
||||
|
||||
|
||||
## How to use llama-vscode
|
||||
|
||||
### Overview
|
||||
|
|
@ -312,6 +247,8 @@ If you are an existing user - you could continue using llama-vscode as before.
|
|||
|
||||
For more details - select 'View Documentation' from llama-vscode menu
|
||||
|
||||
|
||||
|
||||
## Llama Agent
|
||||
|
||||
### Requred servers
|
||||
|
|
@ -352,6 +289,8 @@ https://github.com/user-attachments/assets/dd9da21a-6f57-477d-a55c-e4ff60b1ecb8
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
## Use as local AI runner (as LM Studio, Ollama, etc.)
|
||||
|
||||
### Overview
|
||||
|
|
@ -371,6 +310,8 @@ Enjoy talking with local AI.
|
|||
|
||||
https://github.com/user-attachments/assets/e75e96de-878b-43db-a45b-47cc0c554697
|
||||
|
||||
|
||||
|
||||
## Manage envs
|
||||
|
||||
### Requred servers
|
||||
|
|
@ -379,7 +320,7 @@ https://github.com/user-attachments/assets/e75e96de-878b-43db-a45b-47cc0c554697
|
|||
### Overview
|
||||
Agent is combination of system prompt and tools. If an agent is selected, it will be used by the Llama Agent UI. On slecting and agent, the selected llama-vscode tools are updated.
|
||||
|
||||
They have properties: name, description, system prompt, tools.
|
||||
They have properties: name, description, syste prompt, tools.
|
||||
|
||||
Agent could be added/deleted/viewed/selected/deselected/exported/imported
|
||||
|
||||
|
|
@ -389,12 +330,6 @@ Select "Agents..." from llama-vscode menu
|
|||
- Add agent...
|
||||
Adds an agent
|
||||
|
||||
- Edit agent...
|
||||
Edits an agent
|
||||
|
||||
- Copy agent...
|
||||
Copies an agent
|
||||
|
||||
- Delete agent...
|
||||
Deletes an agent
|
||||
|
||||
|
|
@ -412,6 +347,8 @@ An agent could be exported as a .json files. This file could be shared with othe
|
|||
|
||||
- Import
|
||||
An agent could be imported from a .json file - select a file to import it.
|
||||
|
||||
|
||||
## Manage chat models
|
||||
|
||||
### Requred servers
|
||||
|
|
@ -453,14 +390,13 @@ Deselect the currently selected model. If the model is local, the llama.cpp serv
|
|||
- Add model from huggingface
|
||||
Enter search words to find a model from huggingface. If the model is selected it will be automatically downloaded (if not yet done) and a llama.cpp server will be started with it.
|
||||
|
||||
- Add chat model from OpenAI compatible provider
|
||||
Add chat model from OpenAI compatible provider - OpenRouter or custom (for example local/external llama.cpp server).
|
||||
|
||||
- Export
|
||||
A model could be exported as a .json files. This file could be shared with other users, modified if needed and imported again. Select a model to export it.
|
||||
|
||||
- Import
|
||||
A model could be imported from a .json file - select a file to import it.## Manage envs
|
||||
A model could be imported from a .json file - select a file to import it.
|
||||
|
||||
## Manage envs
|
||||
|
||||
### Requred servers
|
||||
- No servers required
|
||||
|
|
@ -485,6 +421,8 @@ A chat could be exported as a .json file. This file could be shared with other u
|
|||
|
||||
- Import
|
||||
A chat could be imported from a .json file - select a file to import it.
|
||||
|
||||
|
||||
## Manage completion models
|
||||
|
||||
### Requred servers
|
||||
|
|
@ -517,7 +455,7 @@ Select the model you want to delete from the list and delete it.
|
|||
- View
|
||||
Select a model from the list to view all the details for this model
|
||||
|
||||
- Select
|
||||
- Selected
|
||||
Select a model from the list to select it. If the model is a local one (has a command in local start command) a llama.cpp server with this model will be started. Only one completion model could be selected at a time.
|
||||
|
||||
- Deselect
|
||||
|
|
@ -526,14 +464,13 @@ Deselect the currently selected model. If the model is local, the llama.cpp serv
|
|||
- Add model from huggingface
|
||||
Enter search words to find a model from huggingface. If the model is selected it will be automatically downloaded (if not yet done) and a llama.cpp server will be started with it.
|
||||
|
||||
- Add completion model from OpenAI compatible provider
|
||||
Add completion model from OpenAI compatible provider - OpenRouter or custom (for example local/external llama.cpp server).
|
||||
|
||||
- Export
|
||||
A model could be exported as a .json files. This file could be shared with other users, modified if needed and imported again. Select a model to export it.
|
||||
|
||||
- Import
|
||||
A model could be imported from a .json file - select a file to import it.## Manage embeddings
|
||||
A model could be imported from a .json file - select a file to import it.
|
||||
|
||||
## Manage embeddings
|
||||
|
||||
### Requred servers
|
||||
- No servers required
|
||||
|
|
@ -574,14 +511,13 @@ Deselect the currently selected model. If the model is local, the llama.cpp serv
|
|||
- Add model from huggingface
|
||||
Enter search words to find a model from huggingface. If the model is selected it will be automatically downloaded (if not yet done) and a llama.cpp server will be started with it.
|
||||
|
||||
- Add embeddings model from OpenAI compatible provider
|
||||
Add embeddings model from OpenAI compatible provider - OpenRouter or custom (for example local/external llama.cpp server).
|
||||
|
||||
- Export
|
||||
A model could be exported as a .json files. This file could be shared with others used, modified if needed and imported again. Select a model to export it.
|
||||
|
||||
- Import
|
||||
A model could be imported from a .json file - select a file to import it.## Manage envs
|
||||
A model could be imported from a .json file - select a file to import it.
|
||||
|
||||
## Manage envs
|
||||
|
||||
### Requred servers
|
||||
- No servers required
|
||||
|
|
@ -629,6 +565,8 @@ https://github.com/user-attachments/assets/3fb864ad-a010-4d19-97d8-fd7c9ce60494
|
|||
https://github.com/user-attachments/assets/3b8dffcc-bcdc-4981-b181-ffc52fe43075
|
||||
|
||||
|
||||
|
||||
|
||||
## Manage tools models
|
||||
|
||||
### Requred servers
|
||||
|
|
@ -670,14 +608,13 @@ Deselect the currently selected model. If the model is local, the llama.cpp serv
|
|||
- Add model from huggingface
|
||||
Enter search words to find a model from huggingface. If the model is selected it will be automatically downloaded (if not yet done) and a llama.cpp server will be started with it.
|
||||
|
||||
- Add tools model from OpenAI compatible provider
|
||||
Add tools model from OpenAI compatible provider - OpenRouter or custom (for example local/external llama.cpp server).
|
||||
|
||||
- Export
|
||||
A model could be exported as a .json files. This file could be shared with other users, modified if needed and imported again. Select a model to export it.
|
||||
|
||||
- Import
|
||||
A model could be imported from a .json file - select a file to import it.## MCP Support
|
||||
A model could be imported from a .json file - select a file to import it.
|
||||
|
||||
## MCP Support
|
||||
|
||||
### Requred servers
|
||||
- Tools server
|
||||
|
|
@ -697,6 +634,8 @@ llama-vscode could use the the tools from the MCP servers, which are installed i
|
|||
4. Click "Select Tools" from Llama Agent panel and select the tools, which you want to use from your MCP Server
|
||||
|
||||
|
||||
|
||||
|
||||
## Menu
|
||||
|
||||
### Requred servers
|
||||
|
|
@ -712,6 +651,8 @@ OR
|
|||
|
||||
https://github.com/user-attachments/assets/9895924d-1948-4f3c-b52e-2cce453645c8
|
||||
|
||||
|
||||
|
||||
## Model selection
|
||||
|
||||
### What is model selection
|
||||
|
|
@ -725,34 +666,8 @@ There are different ways to select a model
|
|||
- In Llama Agent click the button for selecting a model (completion, chat, embeddings, tools)
|
||||
- In llama-vscode menu select "Completion models..." (or chat, embeddings, tools)
|
||||
- Select an env. This will select the models, which are part of the env
|
||||
## More context files
|
||||
|
||||
### What are AGENTS.md, SOUL.md, and USER.md
|
||||
If in the project folder there are files: AGENTS.md, SOUL.md, and USER.md, they are used to provide additional context to the AI model when a request is sent.
|
||||
AGENTS.md - instructions related with agents
|
||||
SOUL.md - instructions related with the "soul" of the agent (how to behave, what values to follow, etc.)
|
||||
USER.md - information about the user - preferences, additional information, etc.
|
||||
These files are not mandatory. Ther are added because in some systems are quite popular and probably could be reused from there.
|
||||
|
||||
### How to use them
|
||||
Just add one or more of these files to the project folder.
|
||||
## Parallel Completions
|
||||
|
||||
### Overview
|
||||
Llama-vscode generates parallel code completions (default 3) if a version of llama.cpp after December, 6, 2025 (commit c42712b) is used. The next completion is shown by pressing Ctrl+], previous completion is shown by pressing Ctrl+[.
|
||||
The setting max_parallel_completions determines how many completions are generated.
|
||||
|
||||
### How to use it
|
||||
1. Run the completion model and start coding
|
||||
2. When a code completion is shown, press Ctrl+] to show the next completion, Ctrl+[ to show the previous completion
|
||||
3. Alternatively - you could hover over the shown completion and when the toolbar is shown click the arrows to show the other completions.
|
||||
|
||||
|
||||
Settings:
|
||||
- max_parallel_completions: The max number of parallel completions to generate. Default is 3.
|
||||
|
||||
[Screencast from 2026-01-05 15-05-00.webm](https://github.com/user-attachments/assets/41fa92f8-88db-4079-9574-486fb4286c79)
|
||||
|
||||
|
||||
|
||||
## Rules
|
||||
|
||||
### What are rules
|
||||
|
|
@ -765,6 +680,46 @@ The rules are optional. You could use rules file to add instructions to the syst
|
|||
There are two ways to configure rules:
|
||||
- Create a new rules file under name llama-vscode-rules.md in the root of the project.
|
||||
- In llama-vscode setting Agent_rules enter a path to a rules file. It could be relative to the project root or absolute path. If this is specified, the file llama-vscode-rules.md will be ignored.
|
||||
|
||||
|
||||
## Statusbar
|
||||
|
||||
### Requred servers
|
||||
- No servers requred
|
||||
|
||||
### How to use it
|
||||
- View vscode-state
|
||||
- View statistics
|
||||
- Click on "llama-vscode" status bar to show llama-vscode menu
|
||||
|
||||
|
||||
|
||||
https://github.com/user-attachments/assets/8f0b4575-104f-471c-be3f-f3d5b58aeee1
|
||||
|
||||
|
||||
|
||||
## Use cases
|
||||
|
||||
### Overview
|
||||
The use cases below describe how to prepare and use llama-vscode in some specific cases. There are already some configurations for models and env, which could be selected and used directly
|
||||
|
||||
### Only completion used, local server started by llama-vscode
|
||||
- Use the default configuration if it works for you by selecting Env for your case
|
||||
- If you want to use a different one, here is how to prepare it:
|
||||
1. Create completion model - select llama-vscode menu -> "Completion models..." -> "Add completion model from Huggingface", find the model in Huggingface and add it.
|
||||
2. From llama-vscode menu select "Deselect/stop env and models"
|
||||
3. Create an env, which includes only this model - from llama-vscode menu -> "Env..." -> "Add Env...". A panel will be show with buttons for selecting completion, chat, embeddings and tools models. Click "Compl" button and select the newly added model (the name is hf: model_name_from_huggingface). Test if code completion works well. Click button "Add Env" to save the environment.
|
||||
|
||||
### Only completion used, external server
|
||||
Extarnal server could be also a local one, but is not started by llama-vscode on selecting the model. The completion server should support /infill endpoint, which is currently available only by llama.cpp.
|
||||
1. Create a new model - select llama-vscode menu -> "Completion models..." -> "Add completion model...". Enter only name and endpoint.
|
||||
2. From llama-vscode menu select "Deselect/stop env and models"
|
||||
3. Create an env, which includes only this model - from llama-vscode menu -> "Env..." -> "Add Env...". A panel will be show with buttons for selecting completion, chat, embeddings and tools models. Click "Compl" button and select the newly added model. Test if code completion works well. Click button "Add Env" to save the environment.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Setup llama.cpp server for Linux
|
||||
|
||||
1. Download the release files for your OS from [llama.cpp releases.](https://github.com/ggerganov/llama.cpp/releases) (or build from source).
|
||||
|
|
@ -848,6 +803,8 @@ Same like code completion server, but use embeddings model and a little bit diff
|
|||
```bash
|
||||
`llama-server -hf ggml-org/Nomic-Embed-Text-V2-GGUF --port 8010 -ub 2048 -b 2048 --ctx-size 2048 --embeddings`
|
||||
```
|
||||
|
||||
|
||||
### Setup llama.cpp servers for Mac
|
||||
|
||||
Show llama-vscode menu (Ctrl+Shift+M) and select "Install/upgrade llama.cpp" (if not yet done). After that add/select the models you want to use.
|
||||
|
|
@ -926,6 +883,8 @@ Same like code completion server, but use embeddings model and a little bit diff
|
|||
`llama-server -hf ggml-org/Nomic-Embed-Text-V2-GGUF --port 8010 -ub 2048 -b 2048 --ctx-size 2048 --embeddings`
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Setup llama.cpp servers for Windows
|
||||
|
||||
Show llama-vscode menu (Ctrl+Shift+M) and select "Install/upgrade llama.cpp" (if not yet done). After that add/select the models you want to use.
|
||||
|
|
@ -1009,80 +968,5 @@ Same like code completion server, but use embeddings model and a little bit diff
|
|||
```bash
|
||||
`llama-server.exe -hf nomic-embed-text-v2-moe-q8_0.gguf --port 8010 -ub 2048 -b 2048 --ctx-size 2048 --embeddings`
|
||||
```
|
||||
## Skills
|
||||
|
||||
### Overview
|
||||
Llama-vscode support skills (https://agentskills.io/home), which extend the capabilities of the LLM (similar to tools).
|
||||
|
||||
### How to use it
|
||||
1. Set the skills folder in setting skills_folder (if not set, the <project_root>/skills is used)
|
||||
2. Ask the agent for to do something, which requres a skill (or ask details about the skills)
|
||||
|
||||
On sending a user request to the agent, the folder is scanned and the available skills are provided to the LLM. If the LLM decides to use a partiular skill, the skill details are loaded by LLM.
|
||||
|
||||
|
||||
Settings:
|
||||
- skills_folder: The folder where the skills are stored
|
||||
## Statusbar
|
||||
|
||||
### Requred servers
|
||||
- No servers requred
|
||||
|
||||
### How to use it
|
||||
- View vscode-state
|
||||
- View statistics
|
||||
- Click on "llama-vscode" status bar to show llama-vscode menu
|
||||
|
||||
|
||||
|
||||
https://github.com/user-attachments/assets/8f0b4575-104f-471c-be3f-f3d5b58aeee1
|
||||
|
||||
## Subagents
|
||||
|
||||
### What are subagents
|
||||
Subagents are a way to optimize the user of LLM context. Some tasks are be executed in a separate session and only the final result is added to the context of the original agent session.
|
||||
This is implemented with the tool delegate_task. If the delegate_task tool is enabled, the agent could decide to delegate some tasks to subagents. Each agent could be used as a subagent if it's field "Available as Subagent" is checked.
|
||||
|
||||
### How to use them
|
||||
1. Make sure the tool delegate_task is enabled.
|
||||
2. Make sure the agents you want to use as subagents have the field "Available as Subagent" checked and meaningful description.
|
||||
3. Write a prompt, for which it is good idea to use the subagent. Alternatively, you could directly ask in the prompt to use the subagent.
|
||||
|
||||
The agent "Agent creator" makes it easier to create agents (which could be used as subagents).
|
||||
## Update todos tool
|
||||
|
||||
### Overview
|
||||
Llama-vscode provides a tool update_todo_list to the agent for planning and tracking the execution of the user request.
|
||||
|
||||
### How to use it
|
||||
Update todos tool is based on Roocode's tool with the same name (the tool description is copied from Roocode).
|
||||
If the update_todo_list is enabled (selected), the agent could use it for planning non trivial tasks (user requests). This tools is used for both creating and updating the todo items. The todo items are saved in file <project_root>\.llama-vscode-todos.md. The todo items are updated by the agent to track the execution of the plan. This file is removed after the execution of the current user request is finished. If this file is updated by the user, the change might be taken into account by the agent. The content of the file (together with the inital user request) is sent to the agent periodically (every 5-th iteration by default, but this could be changed from setting plan_review_frequency). The agent could overwrite the user changes in the todo items file before reading it.
|
||||
Each time the agent uses the tool, the todo items are shown in the agent chat window. The state of the items tracked with [ ] (not started), [-] (in progres) [x] (finished)
|
||||
Todo items are not reused between the user requests.
|
||||
|
||||
Settings:
|
||||
- plan_review_frequency: Sets how often the todo items are sent to the agent to remind/review what is the current state of the plan
|
||||
- tool_update_todo_list_enabled - controls if the tool is enabled
|
||||
|
||||
<img width="750" height="922" alt="image" src="https://github.com/user-attachments/assets/a4049df0-17da-4c6d-868f-a6bcbfa5f65c" />
|
||||
|
||||
## Use cases
|
||||
|
||||
### Overview
|
||||
The use cases below describe how to prepare and use llama-vscode in some specific cases. There are already some configurations for models and env, which could be selected and used directly
|
||||
|
||||
### Only completion used, local server started by llama-vscode
|
||||
- Use the default configuration if it works for you by selecting Env for your case
|
||||
- If you want to use a different one, here is how to prepare it:
|
||||
1. Create completion model - select llama-vscode menu -> "Completion models..." -> "Add completion model from Huggingface", find the model in Huggingface and add it.
|
||||
2. From llama-vscode menu select "Deselect/stop env and models"
|
||||
3. Create an env, which includes only this model - from llama-vscode menu -> "Env..." -> "Add Env...". A panel will be show with buttons for selecting completion, chat, embeddings and tools models. Click "Compl" button and select the newly added model (the name is hf: model_name_from_huggingface). Test if code completion works well. Click button "Add Env" to save the environment.
|
||||
|
||||
### Only completion used, external server
|
||||
Extarnal server could be also a local one, but is not started by llama-vscode on selecting the model. The completion server should support /infill endpoint, which is currently available only by llama.cpp.
|
||||
1. Create a new model - select llama-vscode menu -> "Completion models..." -> "Add completion model...". Enter only name and endpoint.
|
||||
2. From llama-vscode menu select "Deselect/stop env and models"
|
||||
3. Create an env, which includes only this model - from llama-vscode menu -> "Env..." -> "Add Env...". A panel will be show with buttons for selecting completion, chat, embeddings and tools models. Click "Compl" button and select the newly added model. Test if code completion works well. Click button "Add Env" to save the environment.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import {Completion} from "./completion";
|
|||
import {Logger} from "./logger";
|
||||
import { ChatWithAi } from "./chat-with-ai";
|
||||
import { TextEditor } from "./text-editor";
|
||||
import { FileEditor } from "./file-editor";
|
||||
import { ChatContext } from "./chat-context";
|
||||
import { Prompts } from "./prompts";
|
||||
import { Git } from "./git";
|
||||
|
|
@ -17,23 +16,14 @@ 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";
|
||||
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";
|
||||
import { OpenAiCompModelStrategy } from "./services/openai-comp-model-strategy";
|
||||
import { LlamaChatModelProvider } from "./llama-chat-model-provider";
|
||||
|
||||
export class Application {
|
||||
public static readonly emptyModel = {name: ""};
|
||||
private static instance: Application;
|
||||
public configuration: Configuration;
|
||||
public extraContext: ExtraContext;
|
||||
|
|
@ -46,7 +36,6 @@ export class Application {
|
|||
public logger: Logger
|
||||
public askAi: ChatWithAi
|
||||
public textEditor: TextEditor
|
||||
public fileEditor: FileEditor
|
||||
public chatContext: ChatContext
|
||||
public prompts: Prompts
|
||||
public git: Git
|
||||
|
|
@ -58,23 +47,6 @@ export class Application {
|
|||
public hfModelStrategy: HfModelStrategy
|
||||
public localModelStrategy: LocalModelStrategy
|
||||
public externalModelStrategy: ExternalModelStrategy
|
||||
public openAiCompModelStrategy: OpenAiCompModelStrategy
|
||||
public envService: EnvService
|
||||
public agentService: AgentService
|
||||
public agentCommandService: AgentCommandService
|
||||
public chatService: ChatService
|
||||
public apiKeyService: ApiKeyService
|
||||
public llamaChatModelProvider: LlamaChatModelProvider
|
||||
|
||||
private selectedComplModel: LlmModel = Application.emptyModel
|
||||
private selectedChatModel: LlmModel = Application.emptyModel
|
||||
private selectedEmbeddingsModel: LlmModel = Application.emptyModel
|
||||
private selectedToolsModel: LlmModel = Application.emptyModel
|
||||
private selectedTmpAgentModel: LlmModel = Application.emptyModel
|
||||
private selectedEnv: Env = {name: ""}
|
||||
private selectedAgent: Agent = {name: "", systemInstruction: []}
|
||||
private selectedChat: Chat = {name: "", id: ""}
|
||||
private modelState: Map<string, string> = new Map()
|
||||
|
||||
private constructor(context: vscode.ExtensionContext) {
|
||||
this.configuration = new Configuration()
|
||||
|
|
@ -88,7 +60,6 @@ export class Application {
|
|||
this.logger = new Logger(this)
|
||||
this.askAi = new ChatWithAi(this)
|
||||
this.textEditor = new TextEditor(this)
|
||||
this.fileEditor = new FileEditor(this)
|
||||
this.chatContext = new ChatContext(this)
|
||||
this.prompts = new Prompts(this)
|
||||
this.git = new Git(this)
|
||||
|
|
@ -100,14 +71,7 @@ export class Application {
|
|||
this.hfModelStrategy = new HfModelStrategy(this)
|
||||
this.localModelStrategy = new LocalModelStrategy(this)
|
||||
this.externalModelStrategy = new ExternalModelStrategy(this)
|
||||
this.openAiCompModelStrategy = new OpenAiCompModelStrategy(this)
|
||||
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)
|
||||
this.llamaChatModelProvider = new LlamaChatModelProvider(this);
|
||||
}
|
||||
|
||||
public static getInstance(context: vscode.ExtensionContext): Application {
|
||||
|
|
@ -116,135 +80,5 @@ export class Application {
|
|||
}
|
||||
return Application.instance;
|
||||
}
|
||||
|
||||
getModel = (modelType: ModelType): LlmModel => {
|
||||
let model: LlmModel;
|
||||
switch (modelType) {
|
||||
case ModelType.Completion:
|
||||
model = this.selectedComplModel;
|
||||
break;
|
||||
case ModelType.Chat:
|
||||
model = this.selectedChatModel;
|
||||
break;
|
||||
case ModelType.Embeddings:
|
||||
model = this.selectedEmbeddingsModel;
|
||||
break;
|
||||
case ModelType.Tools:
|
||||
model = this.selectedToolsModel;
|
||||
break;
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
getComplModel = (): LlmModel => {
|
||||
return this.selectedComplModel;
|
||||
}
|
||||
|
||||
getToolsModel = (): LlmModel => {
|
||||
return this.selectedToolsModel;
|
||||
}
|
||||
|
||||
getChatModel = (): LlmModel => {
|
||||
return this.selectedChatModel;
|
||||
}
|
||||
|
||||
getEmbeddingsModel = (): LlmModel => {
|
||||
return this.selectedEmbeddingsModel;
|
||||
}
|
||||
|
||||
getTmpAgentModel = (): LlmModel => {
|
||||
return this.selectedTmpAgentModel;
|
||||
}
|
||||
|
||||
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.selectedChatModel != undefined && this.selectedChatModel.name. trim() != "";
|
||||
}
|
||||
|
||||
isToolsModelSelected = (): boolean => {
|
||||
return this.selectedToolsModel != undefined && this.selectedToolsModel.name. trim() != "";
|
||||
}
|
||||
|
||||
isEmbeddingsModelSelected = (): boolean => {
|
||||
return this.selectedEmbeddingsModel != undefined && this.selectedEmbeddingsModel.name. trim() != "";
|
||||
}
|
||||
|
||||
isTmpAgentModelSelected = (): boolean => {
|
||||
return this.selectedTmpAgentModel != undefined && this.selectedTmpAgentModel.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??Application.emptyModel;
|
||||
break;
|
||||
case ModelType.Chat:
|
||||
this.selectedChatModel = model??Application.emptyModel;
|
||||
break;
|
||||
case ModelType.Embeddings:
|
||||
this.selectedEmbeddingsModel = model??Application.emptyModel;
|
||||
break;
|
||||
case ModelType.Tools:
|
||||
this.selectedToolsModel = model??Application.emptyModel;
|
||||
break;
|
||||
}
|
||||
this.llamaWebviewProvider.updateLlamaView();
|
||||
}
|
||||
|
||||
setModelState = (type: ModelType, state: string) => {
|
||||
this.modelState.set(type, state);
|
||||
this.llamaWebviewProvider.updateModels();
|
||||
}
|
||||
|
||||
getModelState = (type: ModelType): string => {
|
||||
return this.modelState.get(type)??"";
|
||||
}
|
||||
|
||||
setAgentModel = (model: LlmModel | undefined) => {
|
||||
this.selectedTmpAgentModel = model??Application.emptyModel;
|
||||
this.llamaWebviewProvider.updateLlamaView();
|
||||
}
|
||||
|
||||
public setSelectedEnv(env: Env): void {
|
||||
this.selectedEnv = env;
|
||||
this.persistence.setValue(PERSISTENCE_KEYS.SELECTED_ENV, env);
|
||||
this.llamaWebviewProvider.updateLlamaView();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
140
src/architect.ts
140
src/architect.ts
|
|
@ -8,8 +8,7 @@ import {LlamaWebviewProvider} from './llama-webview-provider'
|
|||
import { Utils } from './utils';
|
||||
import { Env, LlmModel } from './types';
|
||||
import { env } from 'process';
|
||||
import { PERSISTENCE_KEYS, SETTING_NAME_FOR_LIST, UiView } from './constants';
|
||||
import {LlamaChatModelProvider} from "./llama-chat-model-provider";
|
||||
import { SETTING_NAME_FOR_LIST } from './constants';
|
||||
|
||||
export class Architect {
|
||||
private app: Application
|
||||
|
|
@ -17,7 +16,6 @@ export class Architect {
|
|||
constructor(application: Application) {
|
||||
this.app = application;
|
||||
}
|
||||
|
||||
|
||||
init = async () => {
|
||||
// Start indexing workspace files
|
||||
|
|
@ -27,47 +25,62 @@ export class Architect {
|
|||
this.app.menu.showHowToUseLlamaVscode();
|
||||
this.app.persistence.setGlobalValue("isFirstStart", false)
|
||||
}
|
||||
const currentVersion = vscode.extensions.getExtension('ggml-org.llama-vscode')?.packageJSON?.version as string | undefined;
|
||||
const storedVersion = this.app.persistence.getGlobalValue(PERSISTENCE_KEYS.EXTENSION_VERSION) as string | undefined;
|
||||
if (currentVersion && storedVersion && currentVersion !== storedVersion) {
|
||||
vscode.window.showInformationMessage(this.app.configuration.getUiText(`llama-vscode extension is updated.`) ?? "");
|
||||
}
|
||||
if (currentVersion) {
|
||||
this.app.persistence.setGlobalValue(PERSISTENCE_KEYS.EXTENSION_VERSION, currentVersion);
|
||||
}
|
||||
await this.installUpgradeLlamaCpp(isFirstStart);
|
||||
if (this.app.configuration.env_start_last_used){
|
||||
let lastEnv = this.app.persistence.getValue("selectedEnv")
|
||||
if (lastEnv) {
|
||||
if (this.app.configuration.env_start_last_used_confirm) {
|
||||
let [shouldSelect, dontAskAgain] = await Utils.showYesYesdontaskNoDialog("You are about the select the env below. If there are local models inside, they will be downloaded (if not yet done) and llama.cpp server(s) will be started. \n\n" +
|
||||
this.app.envService.getEnvDetailsAsString(lastEnv) +
|
||||
this.app.menu.getEnvDetailsAsString(lastEnv) +
|
||||
"\n\n Do you want to continue?"
|
||||
);
|
||||
if (shouldSelect) this.app.envService.selectStartEnv(lastEnv, false);
|
||||
if (shouldSelect) this.app.menu.selectEnv(lastEnv, false);
|
||||
if (dontAskAgain) this.app.configuration.updateConfigValue("env_start_last_used_confirm", false);
|
||||
} else {
|
||||
this.app.envService.selectStartEnv(lastEnv, false);
|
||||
this.app.menu.selectEnv(lastEnv, false);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
let lastChat = this.app.persistence.getValue(PERSISTENCE_KEYS.SELECTED_CHAT)
|
||||
if (lastChat) this.app.chatService.selectUpdateChat(lastChat)
|
||||
let lastAgent = this.app.persistence.getValue(PERSISTENCE_KEYS.SELECTED_AGENT)
|
||||
if (lastAgent) this.app.agentService.selectAgent(lastAgent)
|
||||
let lastChat = this.app.persistence.getValue("selectedChat")
|
||||
if (lastChat) this.app.menu.selectUpdateChat(lastChat)
|
||||
let lastAgent = this.app.persistence.getValue("selectedAgent")
|
||||
if (lastAgent) this.app.menu.selectAgent(lastAgent)
|
||||
this.app.tools.init()
|
||||
}
|
||||
|
||||
setOnSaveDeleteFileForDb = (context: vscode.ExtensionContext) => {
|
||||
const saveListener = vscode.workspace.onDidSaveTextDocument(async (document) => {
|
||||
this.app.chatContext.udpateFileIndexing(document.uri.fsPath, document.getText());
|
||||
try {
|
||||
if (!this.app.configuration.rag_enabled || this.app.configuration.rag_max_files <= 0) return;
|
||||
if (!this.app.chatContext.isImageOrVideoFile(document.uri.toString())){
|
||||
// Update after a delay and only if the file is not changed in the meantime to avoid too often updates
|
||||
let updateTime = Date.now()
|
||||
let fileProperties = this.app.chatContext.getFileProperties(document.uri.toString())
|
||||
if (fileProperties) fileProperties.updated = updateTime;
|
||||
setTimeout(async () => {
|
||||
if (fileProperties && fileProperties.updated > updateTime ) {
|
||||
return;
|
||||
}
|
||||
this.app.chatContext.addDocument(document.uri.toString(), document.getText());
|
||||
}, 5000);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to add document to RAG:', error);
|
||||
}
|
||||
});
|
||||
context.subscriptions.push(saveListener);
|
||||
|
||||
// Add file delete listener for RAG
|
||||
const deleteListener = vscode.workspace.onDidDeleteFiles(async (event) => {
|
||||
await this.app.chatContext.removeFileIndexing(event);
|
||||
if (!this.app.configuration.rag_enabled || this.app.configuration.rag_max_files <= 0) return;
|
||||
for (const file of event.files) {
|
||||
try {
|
||||
await this.app.chatContext.removeDocument(file.toString());
|
||||
} catch (error) {
|
||||
console.error('Failed to remove document from RAG:', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
context.subscriptions.push(deleteListener);
|
||||
}
|
||||
|
|
@ -79,6 +92,7 @@ export class Architect {
|
|||
if (this.app.configuration.isRagConfigChanged(event)) this.init();
|
||||
if (this.app.configuration.isToolChanged(event)) this.app.tools.init();
|
||||
if (this.app.configuration.isEnvViewSettingChanged(event)) this.app.llamaWebviewProvider.updateLlamaView();
|
||||
vscode.window.showInformationMessage(this.app.configuration.getUiText(`llama-vscode extension is updated.`)??"");
|
||||
});
|
||||
context.subscriptions.push(configurationChangeDisp);
|
||||
}
|
||||
|
|
@ -107,36 +121,6 @@ export class Architect {
|
|||
context.subscriptions.push(changeActiveTextEditorDisp)
|
||||
}
|
||||
|
||||
registerCommandSelectNextSuggestion = (context: vscode.ExtensionContext) => {
|
||||
const selectNextSuggestionCommand = vscode.commands.registerCommand(
|
||||
'extension.selectNextSuggestion',
|
||||
async () => {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
await vscode.commands.executeCommand('editor.action.inlineSuggest.showNext');
|
||||
await this.app.completion.increaseSuggestionIndex();
|
||||
}
|
||||
);
|
||||
context.subscriptions.push(selectNextSuggestionCommand);
|
||||
}
|
||||
|
||||
registerCommandSelectPreviousSuggestion = (context: vscode.ExtensionContext) => {
|
||||
const selectPreviousSuggestionCommand = vscode.commands.registerCommand(
|
||||
'extension.selectPreviousSuggestion',
|
||||
async () => {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
await vscode.commands.executeCommand('editor.action.inlineSuggest.showPrevious');
|
||||
await this.app.completion.decreaseSuggestionIndex();
|
||||
}
|
||||
);
|
||||
context.subscriptions.push(selectPreviousSuggestionCommand);
|
||||
}
|
||||
|
||||
registerCommandAcceptFirstLine = (context: vscode.ExtensionContext) => {
|
||||
const acceptFirstLineCommand = vscode.commands.registerCommand(
|
||||
'extension.acceptFirstLine',
|
||||
|
|
@ -186,16 +170,6 @@ export class Architect {
|
|||
context.subscriptions.push(rungBufferUpdateDisposable);
|
||||
}
|
||||
|
||||
setPeriodicModelsHealthUpdate = (context: vscode.ExtensionContext) => {
|
||||
const modelsHealthIntervalId = setInterval(this.app.modelService.periodicModelHealthUpdate, this.app.configuration.health_check_interval_s * 1000);
|
||||
const modelsHealthUpdateDisposable = {
|
||||
dispose: () => {
|
||||
clearInterval(modelsHealthIntervalId);
|
||||
}
|
||||
};
|
||||
context.subscriptions.push(modelsHealthUpdateDisposable);
|
||||
}
|
||||
|
||||
setOnSaveFile = (context: vscode.ExtensionContext) => {
|
||||
const onSaveDocDisposable = vscode.workspace.onDidSaveTextDocument(this.app.extraContext.handleDocumentSave);
|
||||
context.subscriptions.push(onSaveDocDisposable);
|
||||
|
|
@ -212,22 +186,6 @@ export class Architect {
|
|||
);
|
||||
}
|
||||
|
||||
registerLlavaVscodeModelProvider = (context: vscode.ExtensionContext) => {
|
||||
// Register the llama.vscode language model chat provider for GitHub Copilot Chat
|
||||
|
||||
context.subscriptions.push(vscode.lm.registerLanguageModelChatProvider(
|
||||
'llama-vscode',
|
||||
this.app.llamaChatModelProvider
|
||||
));
|
||||
context.subscriptions.push(vscode.workspace.onDidChangeConfiguration((event) => {
|
||||
if (event.affectsConfiguration('llama-vscode.endpoint_chat')
|
||||
|| event.affectsConfiguration('llama-vscode.endpoint_tools')
|
||||
|| event.affectsConfiguration('llama-vscode.ai_api_version')) {
|
||||
this.app.llamaChatModelProvider.notifyModelsChanged();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
registerGenarateCommitMsg = (context: vscode.ExtensionContext) => {
|
||||
const generateCommitCommand = vscode.commands.registerCommand(
|
||||
'extension.generateGitCommitMessage',
|
||||
|
|
@ -405,19 +363,6 @@ export class Architect {
|
|||
context.subscriptions.push(editSelectedTextDisposable);
|
||||
}
|
||||
|
||||
registerCommandEditAllSearchFiles = (context: vscode.ExtensionContext) => {
|
||||
const editAllSearchFilesDisposable = vscode.commands.registerCommand('extension.editAllSearchFiles', async () => {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
if (!editor) {
|
||||
vscode.window.showErrorMessage('No active editor!');
|
||||
return;
|
||||
}
|
||||
await this.app.fileEditor.showEditAllSearchFilesPrompt(editor);
|
||||
});
|
||||
context.subscriptions.push(editAllSearchFilesDisposable);
|
||||
}
|
||||
|
||||
|
||||
registerCommandAcceptTextEdit = (context: vscode.ExtensionContext) => {
|
||||
const acceptTextEditDisposable = vscode.commands.registerCommand('extension.acceptTextEdit', async () => {
|
||||
await this.app.textEditor.acceptSuggestion();
|
||||
|
|
@ -453,8 +398,7 @@ export class Architect {
|
|||
'extension.showLlamaWebview',
|
||||
async () => {
|
||||
vscode.commands.executeCommand('llama-vscode.webview.focus');
|
||||
if (this.app.isToolsModelSelected() || this.app.configuration.endpoint_tools) this.app.llamaWebviewProvider.setView(UiView.Agent)
|
||||
else this.app.llamaWebviewProvider.setView(UiView.Environment)
|
||||
this.app.llamaWebviewProvider.setView("agent")
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
if (editor && editor.selection) {
|
||||
let fileLongName = editor.document.fileName;
|
||||
|
|
@ -503,17 +447,13 @@ export class Architect {
|
|||
context.subscriptions.push(postMessageCommand);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private async installUpgradeLlamaCpp(isFirstStart: any) {
|
||||
if (!this.app.configuration.ask_install_llamacpp) return;
|
||||
let result = await Utils.executeTerminalCommand("llama-server --version");
|
||||
if (result.includes("command not found") || result.includes("is not recognized")) {
|
||||
let questionInstall = "llama.cpp will be installed as it is requred by llama-vscode extension.";
|
||||
if (process.platform == 'win32') questionInstall += "\nVS Code will be restarted.";
|
||||
let [shouldInstall, shouldStopAsking] = await Utils.showYesNoNodontAskDialog(questionInstall, "Confirm");
|
||||
let shouldInstall = await Utils.showUserChoiceDialog(questionInstall, "Confirm");
|
||||
if (shouldInstall) {
|
||||
await this.app.menu.installLlamacpp();
|
||||
this.app.persistence.setGlobalValue("last_llama_cpp", (new Date()).toISOString());
|
||||
|
|
@ -523,6 +463,8 @@ export class Architect {
|
|||
}, 2000);
|
||||
}
|
||||
} else {
|
||||
let questionStopAskingLlamaCppInstall = "Do you prefer to stop getting a suggestion to install llama.cpp?"
|
||||
let shouldStopAsking = await Utils.showUserChoiceDialog(questionStopAskingLlamaCppInstall, "Yes");
|
||||
if (shouldStopAsking) this.app.configuration.updateConfigValue("ask_install_llamacpp", false);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -530,7 +472,7 @@ export class Architect {
|
|||
let lastUpgradeDateStr = this.app.persistence.getGlobalValue("last_llama_cpp");
|
||||
if (!lastUpgradeDateStr || Utils.isTimeToUpgrade(new Date(lastUpgradeDateStr), new Date(), this.app.configuration.ask_upgrade_llamacpp_hours)) {
|
||||
let questionInstall = "Do you want to upgrade llama.cpp (used for running local models)? (recommended).";
|
||||
let [shouldInstall, shouldStopAsking] = await Utils.showYesNoNodontAskDialog(questionInstall, "Confirm");
|
||||
let shouldInstall = await Utils.showUserChoiceDialog(questionInstall, "Confirm"); //yes, don't ask again
|
||||
if (shouldInstall) {
|
||||
await this.app.menu.installLlamacpp();
|
||||
this.app.persistence.setGlobalValue("last_llama_cpp", (new Date()).toISOString());
|
||||
|
|
@ -549,6 +491,8 @@ export class Architect {
|
|||
this.app.configuration.updateConfigValue(SETTING_NAME_FOR_LIST.ENVS, envs);
|
||||
}
|
||||
} else {
|
||||
let questionStopAskingLlamaCppUpgrade = "Do you prefer to stop getting a suggestion to upgrade llama.cpp?"
|
||||
let shouldStopAsking = await Utils.showUserChoiceDialog(questionStopAskingLlamaCppUpgrade, "Yes");
|
||||
if (shouldStopAsking){
|
||||
if (!lastUpgradeDateStr) this.app.persistence.setGlobalValue("last_llama_cpp", (new Date()).toISOString());
|
||||
this.app.configuration.updateConfigValue("ask_upgrade_llamacpp_hours", 72000); // more than 8 years
|
||||
|
|
@ -570,7 +514,7 @@ export class Architect {
|
|||
|
||||
private getChatEndpoint() {
|
||||
let endpoint = this.app.configuration.endpoint_chat;
|
||||
let chatModel = this.app.getChatModel();
|
||||
let chatModel = this.app.menu.getChatModel();
|
||||
if (chatModel && chatModel.endpoint) endpoint = chatModel.endpoint;
|
||||
return endpoint;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,10 +53,10 @@ 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.getEmbeddingsModel().endpoint && this.app.getEmbeddingsModel().endpoint?.trim() != "")
|
||||
if ((this.app.menu.getEmbeddingsModel().endpoint && this.app.menu.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(prompt, topChunksBm25, this.app.configuration.rag_max_embedding_filter_chunks);
|
||||
topContextChunks = await this.cosineSimilarityRank(query, topChunksBm25, this.app.configuration.rag_max_embedding_filter_chunks);
|
||||
} else {
|
||||
vscode.window.showInformationMessage('No embeddings server. Filtering chunks with embeddings will be skipped.');
|
||||
topContextChunks = topChunksBm25.slice(0, 5);
|
||||
|
|
@ -275,9 +275,9 @@ export class ChatContext {
|
|||
}
|
||||
}
|
||||
|
||||
async removeDocument(filePath: string) {
|
||||
this.removeChunkEntries(filePath);
|
||||
this.filesProperties.delete(filePath);
|
||||
async removeDocument(uri: string) {
|
||||
this.removeChunkEntries(uri);
|
||||
this.filesProperties.delete(uri);
|
||||
}
|
||||
|
||||
async indexWorkspaceFiles() {
|
||||
|
|
@ -408,37 +408,4 @@ export class ChatContext {
|
|||
const regex = /@([a-zA-Z0-9_.-]+)(?=[,.?!\s]|$)/g;
|
||||
return [...text.matchAll(regex)].map(match => match[1]);
|
||||
}
|
||||
|
||||
public udpateFileIndexing(filePath: string, fileContent: string) {
|
||||
try {
|
||||
if (this.app.configuration.rag_enabled && this.app.configuration.rag_max_files > 0) {
|
||||
if (!this.isImageOrVideoFile(filePath)) {
|
||||
// Update after a delay and only if the file is not changed in the meantime to avoid too often updates
|
||||
let updateTime = Date.now();
|
||||
let fileProperties = this.getFileProperties(filePath);
|
||||
if (fileProperties) fileProperties.updated = updateTime;
|
||||
setTimeout(async () => {
|
||||
if (fileProperties && fileProperties.updated > updateTime) {
|
||||
return;
|
||||
}
|
||||
this.addDocument(filePath, fileContent);
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to add/update document to RAG:', error);
|
||||
}
|
||||
}
|
||||
|
||||
public async removeFileIndexing(event: vscode.FileDeleteEvent) {
|
||||
if (this.app.configuration.rag_enabled && this.app.configuration.rag_max_files > 0) {
|
||||
for (const file of event.files) {
|
||||
try {
|
||||
await this.removeDocument(file.fsPath);
|
||||
} catch (error) {
|
||||
console.error('Failed to remove document from RAG:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,20 +55,22 @@ export class ChatWithAi {
|
|||
? this.app.configuration.endpoint_chat + "/"
|
||||
: this.app.configuration.endpoint_tools ? this.app.configuration.endpoint_tools + "/" : "";
|
||||
|
||||
let chatModel = this.app.getChatModel();
|
||||
if (!this.app.isChatModelSelected() && !this.app.configuration.endpoint_chat) chatModel = this.app.getToolsModel();
|
||||
let chatModel = this.app.menu.getChatModel();
|
||||
if (!this.app.menu.isChatModelSelected()) chatModel = this.app.menu.getToolsModel();
|
||||
if (chatModel.endpoint) {
|
||||
const chatEndpoint = Utils.trimTrailingSlash(chatModel.endpoint)
|
||||
targetUrl = chatEndpoint ? chatEndpoint + "/" : "";
|
||||
}
|
||||
if (!targetUrl) {
|
||||
await Utils.suggestModelSelection(
|
||||
"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.",
|
||||
"After the chat/tools model is loaded, try again opening Chat with AI.",
|
||||
"No endpoint for the chat or tools model. Select a chat or tools model run on llama-server or an env with chat or tools model or enter the endpoint of a running llama.cpp server with chat model in setting endpoint_chat. ",
|
||||
this.app
|
||||
);
|
||||
return
|
||||
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();
|
||||
vscode.window.showInformationMessage("After the chat/tools model is loaded, try again opening Chat with AI.")
|
||||
return;
|
||||
} else {
|
||||
vscode.window.showErrorMessage("No endpoint for the chat or tools model. Select a chat or tools model run on llama-server or an env with chat or tools model or enter the endpoint of a running llama.cpp server with chat model in setting endpoint_chat. ")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (withContext){
|
||||
|
|
@ -128,9 +130,19 @@ export class ChatWithAi {
|
|||
// console.log("onDidReceiveMessage: " + message.text);
|
||||
}
|
||||
});
|
||||
// Wait for the page to load before sending message
|
||||
if (query) extraCont += await this.prepareRagContext(query);
|
||||
setTimeout(async () => {
|
||||
if (aiPanel) aiPanel.webview.postMessage({ command: 'setText', text: queryToSend, context: extraCont });
|
||||
}, Math.max(0, 3000 - (Date.now() - createWebviewTimeInMs)));
|
||||
} else {
|
||||
aiPanel.reveal();
|
||||
this.lastActiveEditor = editor;
|
||||
if (query) extraCont += await this.prepareRagContext(query);
|
||||
// Wait for the page to load before sending message
|
||||
setTimeout(async () => {
|
||||
if (aiPanel) aiPanel.webview.postMessage({ command: 'setText', text: queryToSend, context: extraCont });
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -147,12 +159,46 @@ export class ChatWithAi {
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="default-src 'self' https: http: data: blob: 'unsafe-inline' 'unsafe-eval';
|
||||
connect-src 'self' https: http: ws: wss:;
|
||||
frame-src 'self' https: http:;">
|
||||
<title>llama.cpp server UI</title>
|
||||
<script>
|
||||
// Initialize the VS Code API
|
||||
const vscode = acquireVsCodeApi();
|
||||
vscode.postMessage({ command: 'jsAction', text: 'vscode javascript object created' });
|
||||
|
||||
// Listen for messages from the extension
|
||||
window.addEventListener('message', (event) => {
|
||||
vscode.postMessage({ command: 'jsAction', text: 'message received' });
|
||||
|
||||
const { command, text, context } = event.data; // Extract the command and text from the event
|
||||
if (command === 'setText') {
|
||||
vscode.postMessage({ command: 'jsAction', text: 'command setText received' });
|
||||
|
||||
const iframe = document.getElementById('askAiIframe');
|
||||
if (iframe) {
|
||||
vscode.postMessage({ command: 'jsAction', text: 'askAiIframe obtained' });
|
||||
iframe.contentWindow.postMessage({ command: 'setText', text: text, context: context }, '*');
|
||||
vscode.postMessage({ command: 'jsAction', text: text });
|
||||
}
|
||||
}
|
||||
if (command === 'escapePressed') {
|
||||
vscode.postMessage({ command: 'jsAction', text: 'command escape pressed' });
|
||||
vscode.postMessage({ command: 'escapePressed' });
|
||||
}
|
||||
if (command === 'jsAction') {
|
||||
vscode.postMessage({ command: 'jsAction', text: text });
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for key events in the iframe
|
||||
window.addEventListener('keydown', (event) => {
|
||||
vscode.postMessage({ command: 'jsAction', text: 'keydown event received' });
|
||||
if (event.key === 'Escape') {
|
||||
// Send a message to the extension when Escape is pressed
|
||||
vscode.postMessage({ command: 'escapePressed', text: "" });
|
||||
vscode.postMessage({ command: 'jsAction', text: "Escabe key pressed..." });
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
body, html {
|
||||
margin: 0;
|
||||
|
|
@ -169,7 +215,7 @@ export class ChatWithAi {
|
|||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<iframe src="${url}" sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-modals" id="askAiIframe"></iframe>
|
||||
<iframe src="${url}" id="askAiIframe"></iframe>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -4,19 +4,18 @@ import vscode from "vscode";
|
|||
import {Utils} from "./utils";
|
||||
|
||||
interface CompletionDetails {
|
||||
completions: string[];
|
||||
completion: string;
|
||||
position: vscode.Position;
|
||||
inputPrefix: string;
|
||||
inputSuffix: string;
|
||||
prompt: string;
|
||||
complIndex: number;
|
||||
}
|
||||
|
||||
export class Completion {
|
||||
private app: Application
|
||||
private isRequestInProgress = false
|
||||
isForcedNewRequest = false
|
||||
lastCompletion: CompletionDetails = {completions: [], complIndex: 0, position: new vscode.Position(0, 0), inputPrefix: "", inputSuffix: "", prompt: ""};
|
||||
lastCompletion: CompletionDetails = {completion: "", position: new vscode.Position(0, 0), inputPrefix: "", inputSuffix: "", prompt: ""};
|
||||
|
||||
constructor(application: Application) {
|
||||
this.app = application;
|
||||
|
|
@ -30,15 +29,6 @@ export class Completion {
|
|||
return null;
|
||||
}
|
||||
|
||||
// Debounce: wait for the user to pause typing before hitting the backend
|
||||
if (context.triggerKind == vscode.InlineCompletionTriggerKind.Automatic && this.app.configuration.debounce_ms > 0) {
|
||||
await Utils.delay(this.app.configuration.debounce_ms);
|
||||
if (token.isCancellationRequested) {
|
||||
this.app.logger.addEventLog(group, "DEBOUNCE_CANCELLATION_RETURN", "")
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Start only if the previous request is finiched
|
||||
while (this.isRequestInProgress) {
|
||||
await Utils.delay(this.app.configuration.DELAY_BEFORE_COMPL_REQUEST);
|
||||
|
|
@ -76,8 +66,8 @@ export class Completion {
|
|||
try {
|
||||
let data: LlamaResponse | undefined
|
||||
let hashKey = this.app.lruResultCache.getHash(inputPrefix + "|" + inputSuffix + "|" + prompt)
|
||||
let completions = this.getCachedCompletion(hashKey, inputPrefix, inputSuffix, prompt)
|
||||
let isCachedResponse = !this.isForcedNewRequest && completions != undefined
|
||||
let completion = this.getCachedCompletion(hashKey, inputPrefix, inputSuffix, prompt)
|
||||
let isCachedResponse = !this.isForcedNewRequest && completion != undefined
|
||||
if (!isCachedResponse) {
|
||||
this.isForcedNewRequest = false
|
||||
if (token.isCancellationRequested){
|
||||
|
|
@ -88,56 +78,46 @@ export class Completion {
|
|||
this.app.statusbar.showThinkingInfo();
|
||||
|
||||
data = await this.app.llamaServer.getFIMCompletion(inputPrefix, inputSuffix, prompt, this.app.extraContext.chunks, nindent)
|
||||
if (data != undefined) completions = this.getComplFromContent(data);
|
||||
else completions = undefined
|
||||
if (data != undefined) completion = data.content;
|
||||
else completion = undefined
|
||||
}
|
||||
if (completions == undefined || completions.length == 0){
|
||||
if (completion == undefined || completion.trim() == ""){
|
||||
this.app.statusbar.showInfo(undefined);
|
||||
this.isRequestInProgress = false
|
||||
this.app.logger.addEventLog(group, "NO_SUGGESTION_RETURN", "")
|
||||
return [];
|
||||
}
|
||||
|
||||
let newCompletions: string[] = []
|
||||
let firstComplLines: string[] = []
|
||||
let suggestionLines = completion.split(/\r?\n/)
|
||||
Utils.removeTrailingNewLines(suggestionLines);
|
||||
|
||||
for (let compl of completions){
|
||||
let suggestionLines = compl.split(/\r?\n/)
|
||||
Utils.removeTrailingNewLines(suggestionLines);
|
||||
|
||||
if (this.shouldDiscardSuggestion(suggestionLines, document, position, linePrefix, lineSuffix)) {
|
||||
continue
|
||||
} else {
|
||||
compl = this.updateSuggestion(suggestionLines, lineSuffix);
|
||||
newCompletions.push(compl);
|
||||
if (firstComplLines.length == 0) firstComplLines = suggestionLines;
|
||||
}
|
||||
}
|
||||
if (newCompletions.length == 0){
|
||||
if (this.shouldDiscardSuggestion(suggestionLines, document, position, linePrefix, lineSuffix)) {
|
||||
this.app.statusbar.showInfo(undefined);
|
||||
this.isRequestInProgress = false
|
||||
this.app.logger.addEventLog(group, "DISCARD_SUGGESTION_RETURN", "")
|
||||
return [];
|
||||
this.isRequestInProgress = false
|
||||
this.app.logger.addEventLog(group, "DISCARD_SUGGESTION_RETURN", "")
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!isCachedResponse && newCompletions) this.app.lruResultCache.put(hashKey, newCompletions)
|
||||
this.lastCompletion = this.getCompletionDetails(newCompletions, position, inputPrefix, inputSuffix, prompt);
|
||||
completion = this.updateSuggestion(suggestionLines, lineSuffix);
|
||||
|
||||
if (!isCachedResponse) this.app.lruResultCache.put(hashKey, completion)
|
||||
this.lastCompletion = this.getCompletionDetails(completion, position, inputPrefix, inputSuffix, prompt);
|
||||
|
||||
// Run async as not needed for the suggestion
|
||||
setTimeout(async () => {
|
||||
if (isCachedResponse) this.app.statusbar.showCachedInfo()
|
||||
else this.app.statusbar.showInfo(data);
|
||||
if (!token.isCancellationRequested && lineSuffix.trim() === ""){
|
||||
await this.cacheFutureSuggestion(inputPrefix, inputSuffix, prompt, firstComplLines);
|
||||
await this.cacheFutureAcceptLineSuggestion(inputPrefix, inputSuffix, prompt, firstComplLines);
|
||||
await this.cacheFutureSuggestion(inputPrefix, inputSuffix, prompt, suggestionLines);
|
||||
await this.cacheFutureAcceptLineSuggestion(inputPrefix, inputSuffix, prompt, suggestionLines);
|
||||
}
|
||||
if (!token.isCancellationRequested){
|
||||
this.app.extraContext.addFimContextChunks(position, context, document);
|
||||
}
|
||||
}, 0);
|
||||
this.isRequestInProgress = false
|
||||
this.app.logger.addEventLog(group, "NORMAL_RETURN", firstComplLines[0])
|
||||
return this.getCompletion(newCompletions||[], position, spacesToRemove);
|
||||
this.app.logger.addEventLog(group, "NORMAL_RETURN", suggestionLines[0])
|
||||
return [this.getCompletion(this.removeLeadingSpaces(completion, spacesToRemove), position)];
|
||||
} catch (err) {
|
||||
console.error("Error fetching llama completion:", err);
|
||||
vscode.window.showInformationMessage(this.app.configuration.getUiText(`Error getting response. Please check if llama.cpp server is running.`)??"");
|
||||
|
|
@ -175,36 +155,21 @@ export class Completion {
|
|||
let promptCut = prompt.slice(i)
|
||||
let hash = this.app.lruResultCache.getHash(inputPrefix + "|" + inputSuffix + "|" + newPrompt)
|
||||
let result = this.app.lruResultCache.get(hash)
|
||||
if (result == undefined) continue
|
||||
let completions: string[] = []
|
||||
for (const compl of result){
|
||||
if (compl && promptCut == compl.slice(0,promptCut.length)) {
|
||||
completions.push(compl.slice(prompt.length - newPrompt.length))
|
||||
}
|
||||
}
|
||||
if (completions.length > 0) return completions;
|
||||
if (result != undefined && promptCut == result.slice(0,promptCut.length)) return result.slice(prompt.length - newPrompt.length)
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
getCompletion = (completions: string[],
|
||||
position: vscode.Position,
|
||||
spacesToRemove: number): vscode.InlineCompletionItem[] => {
|
||||
let completionItems: vscode.InlineCompletionItem[] = []
|
||||
for (const completion of completions){
|
||||
const compl: vscode.InlineCompletionItem = new vscode.InlineCompletionItem(
|
||||
this.removeLeadingSpaces(completion, spacesToRemove),
|
||||
new vscode.Range(position, position)
|
||||
)
|
||||
completionItems.push(compl);
|
||||
}
|
||||
|
||||
return completionItems;
|
||||
getCompletion = (completion: string, position: vscode.Position) => {
|
||||
return new vscode.InlineCompletionItem(
|
||||
completion,
|
||||
new vscode.Range(position, position)
|
||||
);
|
||||
}
|
||||
|
||||
private getCompletionDetails = (completions: string[], position: vscode.Position, inputPrefix: string, inputSuffix: string, prompt: string) => {
|
||||
return { completions: completions,complIndex: 0, position: position, inputPrefix: inputPrefix, inputSuffix: inputSuffix, prompt: prompt };
|
||||
private getCompletionDetails = (completion: string, position: vscode.Position, inputPrefix: string, inputSuffix: string, prompt: string) => {
|
||||
return { completion: completion, position: position, inputPrefix: inputPrefix, inputSuffix: inputSuffix, prompt: prompt };
|
||||
}
|
||||
|
||||
// logic for discarding predictions that repeat existing text
|
||||
|
|
@ -276,17 +241,14 @@ export class Completion {
|
|||
let cached_completion = this.app.lruResultCache.get(futureHashKey)
|
||||
if (cached_completion != undefined) return;
|
||||
let futureData = await this.app.llamaServer.getFIMCompletion(futureInputPrefix, futureInputSuffix, futurePrompt, this.app.extraContext.chunks, prompt.length - prompt.trimStart().length);
|
||||
let futureSuggestions = [];
|
||||
let futureSuggestion = "";
|
||||
if (futureData != undefined && futureData.content != undefined && futureData.content.trim() != "") {
|
||||
let suggestions = this.getComplFromContent(futureData);
|
||||
for (let futureSuggestion of suggestions||[]){
|
||||
let suggestionLines = futureSuggestion.split(/\r?\n/)
|
||||
Utils.removeTrailingNewLines(suggestionLines);
|
||||
futureSuggestion = suggestionLines.join('\n')
|
||||
futureSuggestions.push(futureSuggestion)
|
||||
}
|
||||
futureSuggestion = futureData.content;
|
||||
let suggestionLines = futureSuggestion.split(/\r?\n/)
|
||||
Utils.removeTrailingNewLines(suggestionLines);
|
||||
futureSuggestion = suggestionLines.join('\n')
|
||||
let futureHashKey = this.app.lruResultCache.getHash(futureInputPrefix + "|" + futureInputSuffix + "|" + futurePrompt);
|
||||
this.app.lruResultCache.put(futureHashKey, futureSuggestions);
|
||||
this.app.lruResultCache.put(futureHashKey, futureSuggestion);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -300,13 +262,13 @@ export class Completion {
|
|||
let futureSuggestion = suggestionLines.slice(1).join('\n')
|
||||
let cached_completion = this.app.lruResultCache.get(futureHashKey)
|
||||
if (cached_completion != undefined) return;
|
||||
else this.app.lruResultCache.put(futureHashKey, [futureSuggestion])
|
||||
else this.app.lruResultCache.put(futureHashKey, futureSuggestion)
|
||||
}
|
||||
}
|
||||
|
||||
insertNextWord = async (editor: vscode.TextEditor) => {
|
||||
// Retrieve the last inline completion item
|
||||
const lastSuggestion = this.lastCompletion.completions[this.lastCompletion.complIndex];
|
||||
const lastSuggestion = this.lastCompletion.completion;
|
||||
if (!lastSuggestion) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -332,7 +294,7 @@ export class Completion {
|
|||
|
||||
insertFirstLine = async (editor: vscode.TextEditor) => {
|
||||
// Retrieve the last inline completion item
|
||||
const lastItem = this.lastCompletion.completions[this.lastCompletion.complIndex];
|
||||
const lastItem = this.lastCompletion.completion;
|
||||
if (!lastItem) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -349,33 +311,4 @@ export class Completion {
|
|||
editBuilder.insert(position, insertLine);
|
||||
});
|
||||
}
|
||||
|
||||
increaseSuggestionIndex = async () => {
|
||||
const totalCompletions = this.lastCompletion.completions.length
|
||||
if (totalCompletions > 0){
|
||||
this.lastCompletion.complIndex = (this.lastCompletion.complIndex + 1) % totalCompletions
|
||||
}
|
||||
}
|
||||
|
||||
decreaseSuggestionIndex = async () => {
|
||||
const totalCompletions = this.lastCompletion.completions.length
|
||||
if (totalCompletions > 0){
|
||||
if (this.lastCompletion.complIndex > 0) this.lastCompletion.complIndex--
|
||||
else this.lastCompletion.complIndex = totalCompletions - 1
|
||||
}
|
||||
}
|
||||
|
||||
private getComplFromContent(codeCompletions: any): string[] | undefined {
|
||||
if ("content" in codeCompletions)
|
||||
return [codeCompletions.content??""]
|
||||
|
||||
if (codeCompletions.length > 0){
|
||||
let completions: Set<string> = new Set()
|
||||
for (const compl of codeCompletions){
|
||||
completions.add(compl.content??"")
|
||||
}
|
||||
return Array.from(completions);
|
||||
}
|
||||
else return [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ export class Configuration {
|
|||
new_embeddings_model_host = "127.0.0.1"
|
||||
new_tools_model_host = "127.0.0.1"
|
||||
auto = true;
|
||||
debounce_ms = 0;
|
||||
api_key = "";
|
||||
api_key_chat = "";
|
||||
api_key_tools = "";
|
||||
|
|
@ -47,14 +46,7 @@ export class Configuration {
|
|||
ring_chunk_size = 64;
|
||||
ring_scope = 1024;
|
||||
ring_update_ms = 1000;
|
||||
skills_folder = ""
|
||||
language = "en";
|
||||
health_check_interval_s = 30;
|
||||
health_check_compl_enabled = false;
|
||||
health_check_chat_enabled = false;
|
||||
health_check_embs_enabled = false;
|
||||
health_check_tools_enabled = false;
|
||||
|
||||
|
||||
// experimental - avoid using
|
||||
use_openai_endpoint = false;
|
||||
|
|
@ -74,7 +66,6 @@ export class Configuration {
|
|||
rag_max_context_file_chars = 10000
|
||||
|
||||
tool_run_terminal_command_enabled = true;
|
||||
tool_create_agent_enabled = true;
|
||||
tool_search_source_enabled = true;
|
||||
tool_read_file_enabled = true;
|
||||
tool_list_directory_enabled = true;
|
||||
|
|
@ -93,16 +84,14 @@ export class Configuration {
|
|||
tool_custom_eval_tool_property_description = ""
|
||||
tool_custom_eval_tool_code = "";
|
||||
tool_llama_vscode_help_enabled = true;
|
||||
tool_update_todo_list_enabled = true;
|
||||
tool_delegate_task_enabled = true;
|
||||
tool_save_plan_enabled = false;
|
||||
tool_update_task_enabled = false;
|
||||
tools_max_iterations = 50;
|
||||
plan_review_frequency = 5;
|
||||
tools_log_calls = false;
|
||||
chats_max_history = 50;
|
||||
chats_max_tokens = 64000;
|
||||
chats_summarize_old_msgs = false;
|
||||
chats_msgs_keep = 50
|
||||
max_parallel_completions = 3
|
||||
completion_models_list = new Array();
|
||||
embeddings_models_list = new Array();
|
||||
tools_models_list = new Array();
|
||||
|
|
@ -197,7 +186,6 @@ export class Configuration {
|
|||
this.openai_client_model = String(config.get<string>("openai_client_model"));
|
||||
this.openai_prompt_template = String(config.get<string>("openai_prompt_template"));
|
||||
this.auto = Boolean(config.get<boolean>("auto"));
|
||||
this.debounce_ms = Number(config.get<number>("debounce_ms"));
|
||||
this.api_key = String(config.get<string>("api_key"));
|
||||
this.api_key_chat = String(config.get<string>("api_key_chat"));
|
||||
this.api_key_tools = String(config.get<string>("api_key_tools"));
|
||||
|
|
@ -226,7 +214,6 @@ export class Configuration {
|
|||
this.rag_max_context_files = Number(config.get<number>("rag_max_context_files"));
|
||||
this.rag_max_context_file_chars = Number(config.get<number>("rag_max_context_file_chars"));
|
||||
this.tool_run_terminal_command_enabled = Boolean(config.get<boolean>("tool_run_terminal_command_enabled"));
|
||||
this.tool_create_agent_enabled = Boolean(config.get<boolean>("tool_create_agent_enabled"));
|
||||
this.tool_search_source_enabled = Boolean(config.get<boolean>("tool_search_source_enabled"));
|
||||
this.tool_read_file_enabled = Boolean(config.get<boolean>("tool_read_file_enabled"));
|
||||
this.tool_list_directory_enabled = Boolean(config.get<boolean>("tool_list_directory_enabled"));
|
||||
|
|
@ -238,8 +225,8 @@ export class Configuration {
|
|||
this.tool_edit_file_enabled = Boolean(config.get<boolean>("tool_edit_file_enabled"));
|
||||
this.tool_ask_user_enabled = Boolean(config.get<boolean>("tool_ask_user_enabled"));
|
||||
this.tool_custom_tool_enabled = Boolean(config.get<boolean>("tool_custom_tool_enabled"));
|
||||
this.tool_update_todo_list_enabled = Boolean(config.get<boolean>("tool_update_todo_list_enabled"));
|
||||
this.tool_delegate_task_enabled = Boolean(config.get<boolean>("tool_delegate_task_enabled"));
|
||||
this.tool_save_plan_enabled = Boolean(config.get<boolean>("tool_save_plan_enabled"));
|
||||
this.tool_update_task_enabled = Boolean(config.get<boolean>("tool_update_task_enabled"));
|
||||
this.tool_llama_vscode_help_enabled = Boolean(config.get<boolean>("tool_llama_vscode_help_enabled"));
|
||||
this.tool_custom_tool_description = String(config.get<string>("tool_custom_tool_description"));
|
||||
this.tool_custom_tool_source = String(config.get<string>("tool_custom_tool_source"));
|
||||
|
|
@ -248,14 +235,11 @@ export class Configuration {
|
|||
this.tool_custom_eval_tool_description = String(config.get<string>("tool_custom_eval_tool_description"));
|
||||
this.tool_custom_eval_tool_code = String(config.get<string>("tool_custom_eval_tool_code"));
|
||||
this.tools_max_iterations = Number(config.get<number>("tools_max_iterations"));
|
||||
this.plan_review_frequency = Number(config.get<number>("plan_review_frequency"));
|
||||
this.tools_log_calls = Boolean(config.get<boolean>("tools_log_calls"));
|
||||
this.chats_max_history = Number(config.get<number>("chats_max_history"));
|
||||
this.chats_max_tokens = Number(config.get<number>("chats_max_tokens"));
|
||||
this.max_parallel_completions = Number(config.get<number>("max_parallel_completions"));
|
||||
this.chats_summarize_old_msgs = Boolean(config.get<boolean>("chats_summarize_old_msgs"));
|
||||
this.chats_msgs_keep = Number(config.get<number>("chats_msgs_keep"));
|
||||
this.skills_folder = String(config.get<string>("skills_folder"));
|
||||
this.language = String(config.get<string>("language"));
|
||||
this.disabledLanguages = config.get<string[]>("disabledLanguages") || [];
|
||||
this.enabled = Boolean(config.get<boolean>("enabled", true));
|
||||
|
|
@ -275,11 +259,6 @@ export class Configuration {
|
|||
this.env_start_last_used_confirm = Boolean(config.get<boolean>("env_start_last_used_confirm", true));
|
||||
this.ask_install_llamacpp = Boolean(config.get<boolean>("ask_install_llamacpp", true));
|
||||
this.ask_upgrade_llamacpp_hours = Number(config.get<number>("ask_upgrade_llamacpp_hours"));
|
||||
this.health_check_interval_s = Number(config.get<number>("health_check_interval_s"));
|
||||
this.health_check_compl_enabled = Boolean(config.get<boolean>("health_check_compl_enabled"));
|
||||
this.health_check_chat_enabled = Boolean(config.get<boolean>("health_check_chat_enabled"));
|
||||
this.health_check_embs_enabled = Boolean(config.get<boolean>("health_check_embs_enabled"));
|
||||
this.health_check_tools_enabled = Boolean(config.get<boolean>("health_check_tools_enabled"));
|
||||
};
|
||||
|
||||
getUiText = (uiText: string): string | undefined => {
|
||||
|
|
@ -304,11 +283,7 @@ export class Configuration {
|
|||
isEnvViewSettingChanged = (event: vscode.ConfigurationChangeEvent) => {
|
||||
return event.affectsConfiguration("llama-vscode.enabled")
|
||||
|| event.affectsConfiguration("llama-vscode.rag_enabled")
|
||||
|| event.affectsConfiguration("llama-vscode.env_start_last_used")
|
||||
|| event.affectsConfiguration("llama-vscode.health_check_compl_enabled")
|
||||
|| event.affectsConfiguration("llama-vscode.health_check_chat_enabled")
|
||||
|| event.affectsConfiguration("llama-vscode.health_check_embs_enabled")
|
||||
|| event.affectsConfiguration("llama-vscode.health_check_tools_enabled");
|
||||
|| event.affectsConfiguration("llama-vscode.env_start_last_used");
|
||||
}
|
||||
|
||||
isRagConfigChanged = (event: vscode.ConfigurationChangeEvent) => {
|
||||
|
|
@ -321,7 +296,6 @@ export class Configuration {
|
|||
|
||||
isToolChanged = (event: vscode.ConfigurationChangeEvent) => {
|
||||
return event.affectsConfiguration("llama-vscode.tool_run_terminal_command_enabled")
|
||||
|| event.affectsConfiguration("llama-vscode.tool_create_agent_enabled")
|
||||
|| event.affectsConfiguration("llama-vscode.tool_search_source_enabled")
|
||||
|| event.affectsConfiguration("llama-vscode.tool_list_directory_enabled")
|
||||
|| event.affectsConfiguration("llama-vscode.tool_read_file_enabled")
|
||||
|
|
@ -334,8 +308,6 @@ export class Configuration {
|
|||
|| event.affectsConfiguration("llama-vscode.tool_edit_file_enabled")
|
||||
|| event.affectsConfiguration("llama-vscode.tool_get_diff_enabled")
|
||||
|| event.affectsConfiguration("llama-vscode.tool_llama_vscode_help_enabled")
|
||||
|| event.affectsConfiguration("llama-vscode.tool_update_todo_list_enabled")
|
||||
|| event.affectsConfiguration("llama-vscode.tool_delegate_task_enabled")
|
||||
|| event.affectsConfiguration("llama-vscode.tool_custom_eval_tool_enabled")
|
||||
|| event.affectsConfiguration("llama-vscode.tool_custom_eval_tool_description")
|
||||
|| event.affectsConfiguration("llama-vscode.tool_custom_eval_tool_property_description")
|
||||
|
|
@ -374,7 +346,7 @@ export class Configuration {
|
|||
};
|
||||
}
|
||||
|
||||
this.axiosRequestConfigTools = {};
|
||||
this.axiosRequestConfigTools = {};
|
||||
if (this.api_key_tools != undefined && this.api_key_tools.trim() != "") {
|
||||
this.axiosRequestConfigTools = {
|
||||
headers: {
|
||||
|
|
|
|||
|
|
@ -6,13 +6,6 @@ export enum ModelType {
|
|||
Tools = 'tools'
|
||||
}
|
||||
|
||||
export enum UiView {
|
||||
Agent = 'agent',
|
||||
Environment = 'addenv',
|
||||
Embeddings = 'embeddings',
|
||||
AiRunner = 'airunner'
|
||||
}
|
||||
|
||||
export enum Action {
|
||||
Select = 'select',
|
||||
AddLocal = 'addLocal',
|
||||
|
|
@ -91,9 +84,6 @@ 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",
|
||||
|
|
@ -145,8 +135,6 @@ export const UI_TEXT_KEYS = {
|
|||
showSelectedModels: "Show selected models",
|
||||
showSelectedModelsDescription: "Displays a list of currently selected models",
|
||||
useAsLocalAIRunner: "Use as local AI runner",
|
||||
editMultipleFilesWithAi: "Edit multiple files with AI",
|
||||
editMultipleFilesWithAiDescription: "Asks for glob pattern and prompt and edits with AI the files, which match the glob pattern with the provided prompt.",
|
||||
localAIRunnerDescription: "Download models automatically from Huggingface and chat with them (as LM Studio, Ollama, etc.)",
|
||||
editSettings: "Edit Settings...",
|
||||
apiKeys: "API keys...",
|
||||
|
|
@ -177,8 +165,6 @@ export const UI_TEXT_KEYS = {
|
|||
selectStartAgent: "Select/start agent...",
|
||||
deselectStopAgent: "Deselect/stop agent...",
|
||||
addAgent: "Add agent...",
|
||||
editAgent: "Edit agent...",
|
||||
copyAgent: "Copy agent...",
|
||||
viewAgentDetails: "View agent details...",
|
||||
deleteAgent: "Delete agent...",
|
||||
exportAgent: "Export agent...",
|
||||
|
|
@ -203,7 +189,6 @@ export const UI_TEXT_KEYS = {
|
|||
addLocalCompletionModel: "Add local completion model...",
|
||||
addExternalCompletionModel: "Add external completion model...",
|
||||
addCompletionModelFromHuggingface: "Add completion model from huggingface...",
|
||||
addCompletionOpenAiCompModel: "Add completion model from OpenAI compatible provider...",
|
||||
viewCompletionModelDetails: "View completion model details...",
|
||||
deleteCompletionModel: "Delete completion model...",
|
||||
exportCompletionModel: "Export completion model...",
|
||||
|
|
@ -215,7 +200,6 @@ export const UI_TEXT_KEYS = {
|
|||
addLocalChatModel: "Add local chat model...",
|
||||
addExternalChatModel: "Add external chat model...",
|
||||
addChatModelFromHuggingface: "Add chat model from huggingface...",
|
||||
addChatOpenAiCompModel: "Add chat model from OpenAI compatible provider...",
|
||||
viewChatModelDetails: "View chat model details...",
|
||||
deleteChatModel: "Delete chat model...",
|
||||
exportChatModel: "Export chat model...",
|
||||
|
|
@ -227,7 +211,6 @@ export const UI_TEXT_KEYS = {
|
|||
addLocalEmbeddingsModel: "Add local embeddings model...",
|
||||
addExternalEmbeddingsModel: "Add external embeddings model...",
|
||||
addEmbeddingsModelFromHuggingface: "Add embeddings model from huggingface...",
|
||||
addEmbeddingsOpenAiCompModel: "Add embeddings model from OpenAI compatible provider...",
|
||||
viewEmbeddingsModelDetails: "View embeddings model details...",
|
||||
deleteEmbeddingsModel: "Delete embeddings model...",
|
||||
exportEmbeddingsModel: "Export embeddings model...",
|
||||
|
|
@ -239,21 +222,16 @@ export const UI_TEXT_KEYS = {
|
|||
addLocalToolsModel: "Add local tools model...",
|
||||
addExternalToolsModel: "Add external tools model...",
|
||||
addToolsModelFromHuggingface: "Add tools model from huggingface...",
|
||||
addToolsOpenAiCompModel: "Add tools model from OpenAI compatible provider...",
|
||||
viewToolsModelDetails: "View tools model details...",
|
||||
deleteToolsModel: "Delete tools model...",
|
||||
exportToolsModel: "Export tools model...",
|
||||
importToolsModel: "Import tools model...",
|
||||
|
||||
// Other
|
||||
fileUpdated: "The file is updated"
|
||||
} as const;
|
||||
|
||||
export const PERSISTENCE_KEYS = {
|
||||
SELECTED_CHAT: 'selectedChat' as const,
|
||||
SELECTED_AGENT: 'selectedAgent' as const,
|
||||
SELECTED_ENV: 'selectedEnv' as const,
|
||||
EXTENSION_VERSION: 'extensionVersion' as const,
|
||||
} as const;
|
||||
|
||||
export const SETTING_NAME_FOR_LIST = {
|
||||
|
|
@ -274,20 +252,4 @@ export const PREDEFINED_LISTS_KEYS = {
|
|||
ENVS: SETTING_NAME_FOR_LIST.ENVS,
|
||||
AGENTS: SETTING_NAME_FOR_LIST.AGENTS,
|
||||
AGENT_COMMANDS: SETTING_NAME_FOR_LIST.AGENT_COMMANDS,
|
||||
} as const;
|
||||
|
||||
export enum OpenAiProvidersKeys {
|
||||
OpenRouter = 'OpenRouter...',
|
||||
Custom = 'Custom...'
|
||||
}
|
||||
export const OPENAI_COMP_PROVIDERS = {
|
||||
[OpenAiProvidersKeys.OpenRouter]: "https://openrouter.ai/api",
|
||||
[OpenAiProvidersKeys.Custom]: ""
|
||||
} as const
|
||||
|
||||
export const SUPPORTED_IMG_FILE_EXTS: { [key: string]: string } = {
|
||||
'.jpeg': 'image/jpeg',
|
||||
'.jpg': 'image/jpeg',
|
||||
'.png': 'image/png',
|
||||
'.webp': 'image/webp'
|
||||
} as const;;
|
||||
} as const;
|
||||
|
|
@ -15,14 +15,12 @@ export function activate(context: vscode.ExtensionContext) {
|
|||
app.architect.registerCommandNoCacheCompletion(context);
|
||||
app.architect.setOnSaveFile(context);
|
||||
app.architect.setPeriodicRingBufferUpdate(context);
|
||||
app.architect.setPeriodicModelsHealthUpdate(context);
|
||||
app.architect.setClipboardEvents(context);
|
||||
app.architect.setOnChangeActiveFile(context);
|
||||
app.architect.registerCommandAcceptFirstLine(context);
|
||||
app.architect.registerCommandAcceptFirstWord(context);
|
||||
app.architect.registerCommandShowMenu(context);
|
||||
app.architect.registerCommandEditSelectedText(context);
|
||||
app.architect.registerCommandEditAllSearchFiles(context);
|
||||
app.architect.registerCommandAcceptTextEdit(context);
|
||||
app.architect.registerCommandRejectTextEdit(context);
|
||||
app.architect.setOnSaveDeleteFileForDb(context);
|
||||
|
|
@ -30,12 +28,7 @@ export function activate(context: vscode.ExtensionContext) {
|
|||
app.architect.registerGenarateCommitMsg(context)
|
||||
app.architect.registerCommandKillAgent(context)
|
||||
app.architect.registerWebviewProvider(context)
|
||||
app.architect.registerCommandSelectNextSuggestion(context)
|
||||
app.architect.registerCommandSelectPreviousSuggestion(context)
|
||||
app.architect.registerLlavaVscodeModelProvider(context)
|
||||
app.architect.init()
|
||||
|
||||
|
||||
}
|
||||
|
||||
export async function deactivate() {
|
||||
|
|
|
|||
|
|
@ -1,108 +0,0 @@
|
|||
import * as vscode from 'vscode';
|
||||
import { Application } from './application';
|
||||
import { Utils } from './utils';
|
||||
import { LlamaChatResponse } from './types';
|
||||
|
||||
export class FileEditor {
|
||||
private app: Application;
|
||||
|
||||
constructor(application: Application) {
|
||||
this.app = application;
|
||||
}
|
||||
|
||||
async showEditAllSearchFilesPrompt(editor: vscode.TextEditor) {
|
||||
// Resolve chat or tools model endpoint
|
||||
let chatUrl = this.app.configuration.endpoint_chat;
|
||||
if (!chatUrl) chatUrl = this.app.configuration.endpoint_tools;
|
||||
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 + '/' : '';
|
||||
}
|
||||
if (!chatUrl) {
|
||||
await Utils.suggestModelSelection(
|
||||
'Select a chat or tools model or an env with chat or tools model to edit files with AI.',
|
||||
'After the chat model is loaded, try again editing files with AI.',
|
||||
'No endpoint for the chat model. Select an env with chat model or enter the endpoint of a running llama.cpp server with chat model in setting endpoint_chat.',
|
||||
this.app
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const prompt = await vscode.window.showInputBox({
|
||||
placeHolder: 'Enter instructions for editing files...',
|
||||
prompt: 'How would you like to modify the files? (the instructions will be applied to each file separately)',
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
if (!prompt) return;
|
||||
|
||||
const glob = await vscode.window.showInputBox({
|
||||
placeHolder: '**/*',
|
||||
prompt: 'Enter glob pattern of files to edit (e.g., src/**/*.ts)',
|
||||
ignoreFocusOut: true
|
||||
});
|
||||
if (!glob) return;
|
||||
let shouldContinue = Utils.showYesNoDialog(
|
||||
"You requested an edit of multiple files with AI. " +
|
||||
"\n\nGlob pattern (what files to edit): " + glob +
|
||||
"\nPrompt: " + prompt +
|
||||
"\n\nDo you want to continue?")
|
||||
if (!shouldContinue) return;
|
||||
|
||||
const files = await vscode.workspace.findFiles(glob);
|
||||
if (!files || files.length === 0) {
|
||||
vscode.window.showInformationMessage('No files matched the glob pattern.');
|
||||
return;
|
||||
}
|
||||
|
||||
await vscode.window.withProgress(
|
||||
{
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: 'llama.vscode is editing files with AI',
|
||||
cancellable: true
|
||||
},
|
||||
async (progress, token) => {
|
||||
const total = files.length;
|
||||
let processed = 0;
|
||||
for (const file of files) {
|
||||
if (token.isCancellationRequested) {
|
||||
vscode.window.showInformationMessage(`File editing cancelled after ${processed} of ${total} files.`);
|
||||
break;
|
||||
}
|
||||
if (this.app.chatContext.isImageOrVideoFile(file.fsPath)) continue
|
||||
progress.report({ message: `Editing ${file.fsPath}`, increment: (1 / total) * 100 });
|
||||
|
||||
try {
|
||||
const originalBuffer = await vscode.workspace.fs.readFile(file);
|
||||
const originalText = Buffer.from(originalBuffer).toString('utf8');
|
||||
|
||||
const completion = await this.app.llamaServer.getChatEditCompletion(
|
||||
prompt,
|
||||
originalText,
|
||||
'',
|
||||
this.app.extraContext.chunks,
|
||||
0
|
||||
);
|
||||
|
||||
if (completion?.choices?.[0]?.message?.content) {
|
||||
var edited = completion.choices[0].message.content.trim();
|
||||
edited = Utils.removeFirstAndLastLinesIfBackticks(edited);
|
||||
await vscode.workspace.fs.writeFile(file, Buffer.from(edited, 'utf8'));
|
||||
if (this.app.configuration.rag_enabled) {
|
||||
const document = await vscode.workspace.openTextDocument(file);
|
||||
this.app.chatContext.udpateFileIndexing(document.uri.fsPath, document.getText())
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Failed to edit ${file.fsPath}:`, err);
|
||||
}
|
||||
processed++;
|
||||
}
|
||||
if (!token.isCancellationRequested) {
|
||||
vscode.window.showInformationMessage(`Edited ${processed} of ${total} files.`);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
22
src/git.ts
22
src/git.ts
|
|
@ -13,21 +13,23 @@ 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.getChatModel();
|
||||
if (!this.app.isChatModelSelected() && !this.app.configuration.endpoint_chat) chatModel = this.app.getToolsModel();
|
||||
let chatModel = this.app.menu.getChatModel();
|
||||
if (!this.app.menu.isChatModelSelected()) chatModel = this.app.menu.getToolsModel();
|
||||
if (chatModel.endpoint) {
|
||||
const chatEndpoint = Utils.trimTrailingSlash(chatModel.endpoint)
|
||||
chatUrl = chatEndpoint ? chatEndpoint + "/" : "";
|
||||
}
|
||||
if (!chatUrl) {
|
||||
await Utils.suggestModelSelection(
|
||||
"Select a chat or tools model or an env with chat or tools model to generate a commit message.",
|
||||
"After the chat/tools model is loaded, try again generating commit message.",
|
||||
"No endpoint for the chat model. Select a chat or tools model or an env with chat or tools model or enter the endpoint of a server with chat model in setting endpoint_chat.",
|
||||
this.app
|
||||
);
|
||||
|
||||
return
|
||||
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();
|
||||
vscode.window.showInformationMessage("After the chat/tools model is loaded, try again generating commit message.")
|
||||
return;
|
||||
}
|
||||
else {
|
||||
vscode.window.showErrorMessage("No endpoint for the chat model. Select a chat or tools model or an env with chat or tools model or enter the endpoint of a running llama.cpp server with chat model in setting endpoint_chat. ")
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const gitExtension = vscode.extensions.getExtension('vscode.git')?.exports;
|
||||
|
|
|
|||
320
src/lists.ts
320
src/lists.ts
|
|
@ -47,11 +47,6 @@ export const PREDEFINED_LISTS = new Map<string, any>([
|
|||
"localStartCommand": "llama-server -hf ggml-org/Qwen2.5-Coder-7B-Instruct-Q8_0-GGUF -ngl 99 -ub 1024 -b 1024 --ctx-size 0 --cache-reuse 256 -np 2 --port 8011",
|
||||
"endpoint": "http://127.0.0.1:8011"
|
||||
},
|
||||
{
|
||||
"name": "Qwen2.5-Coder-14B-Instruct-Q8_0-GGUF (> 32GB VRAM)",
|
||||
"localStartCommand": "llama-server -hf ggml-org/Qwen2.5-Coder-14B-Instruct-Q8_0-GGUF -ngl 99 -ub 1024 -b 1024 --ctx-size 0 --cache-reuse 256 -np 2 --port 8011",
|
||||
"endpoint": "http://127.0.0.1:8011"
|
||||
},
|
||||
{
|
||||
"name": "Qwen2.5-Coder-1.5B-Instruct-Q8_0-GGUF (CPU Only)",
|
||||
"localStartCommand": "llama-server -hf ggml-org/Qwen2.5-Coder-1.5B-Instruct-Q8_0-GGUF -ub 1024 -b 1024 -dt 0.1 --ctx-size 0 --cache-reuse 256 -np 2 --port 8011",
|
||||
|
|
@ -79,36 +74,7 @@ export const PREDEFINED_LISTS = new Map<string, any>([
|
|||
"endpoint": "http://127.0.0.1:8010"
|
||||
}
|
||||
]],
|
||||
[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
|
||||
},
|
||||
[PREDEFINED_LISTS_KEYS.TOOLS, [
|
||||
{
|
||||
"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",
|
||||
|
|
@ -117,10 +83,25 @@ export const PREDEFINED_LISTS = new Map<string, any>([
|
|||
"isKeyRequired": false
|
||||
},
|
||||
{
|
||||
"name": "Z.AI: GLM 4.5 Air (free): GLM 4.5 Air - 128.000 context (OpenRouter)",
|
||||
"name": "xAI: Grok 4 Fast (free for limited period), context: 2 000 000",
|
||||
"localStartCommand": "",
|
||||
"endpoint": "https://openrouter.ai/api",
|
||||
"isKeyRequired": true,
|
||||
"aiModel": "z-ai/glm-4.5-air:free"
|
||||
"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
|
||||
},
|
||||
{
|
||||
"name": "Z.AI: GLM 4.5 - 128000 context $0.60/M input tokens $2.20/M output tokens (OpenRouter)",
|
||||
|
|
@ -135,17 +116,11 @@ export const PREDEFINED_LISTS = new Map<string, any>([
|
|||
"aiModel": "z-ai/glm-4.5-air"
|
||||
},
|
||||
{
|
||||
"name": "Qwen: Qwen3 235B A22B Thinking 2507 - 262 144 context $0.118/M input tokens $0.118/M output tokens (OpenRouter)",
|
||||
"name": "Qwen: Qwen3 235B A22B Thinking 2507 - 262.144 context $0.118/M input tokens $0.118/M output tokens (OpenRouter)",
|
||||
"endpoint": "https://openrouter.ai/api",
|
||||
"isKeyRequired": true,
|
||||
"aiModel": "qwen/qwen3-235b-a22b-thinking-2507"
|
||||
},
|
||||
{
|
||||
"name": "Qwen: Qwen3 VL 30B A3B Instruct - 262 144 context $0.15/M input tokens $0.60/M output tokens (OpenRouter)",
|
||||
"endpoint": "https://openrouter.ai/api",
|
||||
"isKeyRequired": true,
|
||||
"aiModel": "qwen/qwen3-vl-30b-a3b-instruct"
|
||||
},
|
||||
{
|
||||
"name": "Qwen: Qwen3 Coder - 262K context $0.30/M input tokens $1.20/M output tokens (OpenRouter)",
|
||||
"endpoint": "https://openrouter.ai/api",
|
||||
|
|
@ -669,7 +644,7 @@ export const PREDEFINED_LISTS = new Map<string, any>([
|
|||
},
|
||||
{
|
||||
"name": "default",
|
||||
"description": "Agent for agentic programming - could answer questions, change/add/delete file, execute terminal commands, etc.",
|
||||
"description": "This is the default agent.",
|
||||
"systemInstruction": [
|
||||
"You are an agent for software development - please keep going until the user’s query is completely resolved, before ending your turn and yielding back to the user.",
|
||||
"Only terminate your turn when you are sure that the problem is solved.",
|
||||
|
|
@ -740,263 +715,8 @@ export const PREDEFINED_LISTS = new Map<string, any>([
|
|||
"delete_file",
|
||||
"get_diff",
|
||||
"edit_file",
|
||||
"ask_user",
|
||||
"update_todo_list",
|
||||
"delegate_task"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Unite test writer",
|
||||
"description": "Writes the unit tests. The input should provide a path to a source file to be tested.",
|
||||
"systemInstruction": [
|
||||
"You are an expert software engineer specializing in writing unit tests. Your task is to generate high‑quality, reliable, and maintainable unit tests based on the user’s instructions and the provided source code. You must infer the programming language, testing framework, and project conventions from the source file and any accompanying context (such as imports, file extensions, or existing test files).",
|
||||
"Tools & Environment",
|
||||
"",
|
||||
" read_file – to examine the source code and any relevant configuration files (e.g., package.json, pom.xml, requirements.txt, Cargo.toml, etc.).",
|
||||
"",
|
||||
" edit_file – to create or modify test files.",
|
||||
"",
|
||||
" run_terminal_command – to execute tests and report results.",
|
||||
"",
|
||||
"Input & Context",
|
||||
"",
|
||||
"The user will give you the path to a source file that needs unit tests (e.g., src/services/user_service.py, lib/user.dart, internal/user.go). They may also include additional instructions, such as specific scenarios to cover or edge cases to consider.",
|
||||
"Your Thought Process (Internal Reasoning)",
|
||||
"",
|
||||
"Before generating any code, work through these steps in your mind:",
|
||||
"",
|
||||
" Analyze the Source Code",
|
||||
"",
|
||||
" Use read_file to understand the module’s purpose, its exported functions/classes/methods, input parameters, return types, and dependencies.",
|
||||
"",
|
||||
" Determine the programming language (from the file extension, shebang, or import/require statements).",
|
||||
"",
|
||||
" Identify all public APIs that need testing.",
|
||||
"",
|
||||
" Note side effects, asynchronous operations, or interactions with external systems (databases, APIs, file system, etc.).",
|
||||
"",
|
||||
" Infer the Testing Conventions",
|
||||
"",
|
||||
" Look for an existing test directory (e.g., test/, tests/, spec/, __tests__/) and the naming pattern of existing test files (e.g., *.test.js, *_test.py, *_spec.rb).",
|
||||
"",
|
||||
" Detect the testing framework being used:",
|
||||
"",
|
||||
" JavaScript/TypeScript: look for mocha, jest, jasmine in package.json.",
|
||||
"",
|
||||
" Python: look for pytest, unittest in imports or config files.",
|
||||
"",
|
||||
" Java: look for JUnit in pom.xml or build.gradle.",
|
||||
"",
|
||||
" Go: look for testing package imports, etc.",
|
||||
"",
|
||||
" Determine the preferred assertion style (e.g., assert module, expect, should, assertThat).",
|
||||
"",
|
||||
" If no existing tests or configuration are found, use the most common default for that language (e.g., pytest for Python, JUnit 5 for Java, go test for Go, Mocha + assert for Node.js).",
|
||||
"",
|
||||
" Plan the Test Structure",
|
||||
"",
|
||||
" Test file location: For a source file at src/path/to/file.ext, the test file should normally be placed at test/path/to/file_test.ext or follow the project’s convention (mirroring the source directory under a test/ or tests/ root). Ensure the directory structure is created if needed.",
|
||||
"",
|
||||
" Plan the outer test suite (e.g., describe('moduleName', ...) in Mocha, a test class in JUnit, or a module‑level docstring in pytest).",
|
||||
"",
|
||||
" Plan nested suites for each function or method.",
|
||||
"",
|
||||
" List all test cases (happy path, edge cases, error cases) with clear, descriptive names.",
|
||||
"",
|
||||
" Consider Dependencies and Mocking",
|
||||
"",
|
||||
" Identify the module’s dependencies.",
|
||||
"",
|
||||
" Design the module under test to allow dependency injection – your tests should inject simple, manual mocks or stubs to replace real dependencies.",
|
||||
"",
|
||||
" Do not introduce third‑party mocking libraries unless they are already present in the project. Rely on manual mocks (e.g., creating test doubles yourself).",
|
||||
"",
|
||||
" Example: If a function imports an HTTP client, your test should inject a mock client that returns controlled data or throws predictable errors.",
|
||||
"",
|
||||
"Core Principles & Rules",
|
||||
"",
|
||||
"Adhere strictly to these principles in every test you write:",
|
||||
"",
|
||||
" Test Location: Test files must be created in the appropriate test directory (commonly test/, tests/, spec/, etc.) mirroring the source structure. Use the naming convention inferred from the project.",
|
||||
"",
|
||||
" Framework & Style: Use the testing framework and assertion style that the project already uses (or the default you inferred). Write idiomatic tests for that language.",
|
||||
"",
|
||||
" Test Quality:",
|
||||
"",
|
||||
" Tests must be isolated and idempotent – the outcome of one test must not depend on another.",
|
||||
"",
|
||||
" Each test should verify one specific behavior.",
|
||||
"",
|
||||
" Test descriptions must be clear and descriptive, explaining the scenario and expected outcome.",
|
||||
"",
|
||||
" Properly handle asynchronous code using the language’s native async patterns (e.g., async/await, Future, Promise). Ensure the test framework waits for completion.",
|
||||
"",
|
||||
" Reset any module state or mocks in setup/teardown hooks (e.g., beforeEach, setUp, @BeforeEach) to guarantee tests can run in any order.",
|
||||
"",
|
||||
" Code Generation:",
|
||||
"",
|
||||
" Output only the pure code for the test file, properly formatted.",
|
||||
"",
|
||||
" Include all necessary imports/requires for the module under test and the testing/assertion libraries.",
|
||||
"",
|
||||
" Import the actual functions/classes from the source file. Mocking is done inside the test, not by mocking the import itself.",
|
||||
"",
|
||||
" No Source Modification: You cannot modify the source code. If the source is untestable due to poor design (e.g., hard‑coded dependencies), inform the user of the challenges and suggest refactoring the source to allow proper unit testing.",
|
||||
"",
|
||||
"Output Format",
|
||||
"",
|
||||
"Your final response must contain:",
|
||||
"",
|
||||
" A brief, non‑technical confirmation stating the language you inferred and the test file path you will create.",
|
||||
"",
|
||||
"Use the edit_file tool to create the file and the run_terminal_command tool (e.g., npx mocha 'test/services/userService.spec.ts') to verify your work, reporting the results back to the user.",
|
||||
"",
|
||||
"Crucially, you cannot modify the source code itself. If the source code is not testable due to poor design (e.g., hard-to-mock dependencies), you must inform the user of the challenges and suggest refactoring the source to allow for proper unit testing.",
|
||||
""
|
||||
],
|
||||
"tools": [
|
||||
"run_terminal_command",
|
||||
"search_source",
|
||||
"read_file",
|
||||
"list_directory",
|
||||
"regex_search",
|
||||
"delete_file",
|
||||
"edit_file",
|
||||
"update_todo_list"
|
||||
],
|
||||
"subagentEnabled": false
|
||||
},
|
||||
{
|
||||
"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 user’s 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 user’s 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"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Agent creator",
|
||||
"description": "Creates new agent. Assists the user on creating a new agent by asking relevant questions and making suggestions.",
|
||||
"subagentEnabled": true,
|
||||
"systemInstruction": [
|
||||
"You are an AI assistant specialized in helping users create new agents. Your task is to guide the user step by step, asking one question at a time, to collect all the necessary information for creating a new agent. Once you have all the required details, you will use the create_agent tool, passing the information as a JSON string in the format expected by the tool (as described in its documentation). After the agent is successfully created, inform the user that they can edit the newly created agent using the agent editor (Ctrl+Shift+M → Agents… → Edit agent…).",
|
||||
"",
|
||||
"Required Information:",
|
||||
"",
|
||||
" name (string): The name of the new agent.",
|
||||
"",
|
||||
" description (string): A brief description of what the agent does.",
|
||||
"",
|
||||
" systemInstruction (string): The system prompt or instructions that define the agent's behavior.",
|
||||
"",
|
||||
"Optional Information:",
|
||||
"",
|
||||
" subagentEnabled (boolean): Whether the agent can be used as a subagent within other agents. Ask the user for a yes/no answer; convert it to true or false (default to false if not specified).",
|
||||
"",
|
||||
" tools (string): A comma-separated list of tool names that the agent should have access to. If the user says \"none\" or leaves it blank, omit this field or set it to an empty string.",
|
||||
"",
|
||||
"Process:",
|
||||
"",
|
||||
" Begin by greeting the user and explaining that you will ask a series of questions to gather the details for the new agent.",
|
||||
"",
|
||||
" Ask for the name first. Wait for the user's response.",
|
||||
"",
|
||||
" After receiving the name, ask for the description.",
|
||||
"",
|
||||
" Then ask for the systemInstruction.",
|
||||
"",
|
||||
" Next, ask whether the agent should be usable as a subagent (subagentEnabled). Prompt for a yes/no answer. If the answer is ambiguous, ask for clarification.",
|
||||
"",
|
||||
" Finally, ask for any tools the agent should have. Prompt for a comma-separated list or indicate that they can say \"none\".",
|
||||
"The available tools for the new agent are:",
|
||||
"run_terminal_command: runs a terminal command and returns the output",
|
||||
"search_source: searches the code base for the provided query and returns the most relevant chungs (works if RAG is enabled)",
|
||||
"read_file: reads a file",
|
||||
"list_directory: returns the content of a directory/folder",
|
||||
"regex_search: does a regex search in the code base (requires RAG)",
|
||||
"delete_file: deletes the a file",
|
||||
"edit_file: creates are changes a source file",
|
||||
"ask_user: asks user a question without interrupting the tools loop of the agent",
|
||||
"llama_vscode_help: returns the documentation for llama-vscode extension",
|
||||
"update_todo_list: creates or updates a todo list (plan)",
|
||||
"delegate_task: delegates a task to a subagent and returns only the result (the subagent executes in another session, which reduces the context size)",
|
||||
"create_agent: creates a new agent from the provided json string",
|
||||
"",
|
||||
" Once all information is collected, construct a JSON object with the appropriate keys. Ensure that boolean values are represented as true or false (without quotes) and that the tools string is included only if provided.",
|
||||
"",
|
||||
" Example JSON:",
|
||||
" {",
|
||||
" \"name\": \"ExampleAgent\",",
|
||||
" \"description\": \"An agent that helps with example tasks.\",",
|
||||
" \"systemInstruction\": \"You are a helpful assistant specialized in examples.\",",
|
||||
" \"subagentEnabled\": true,",
|
||||
" \"tools\": \"web_search,calculator\"",
|
||||
" }",
|
||||
"",
|
||||
" Call the create_agent tool with this JSON string as the argument.",
|
||||
"",
|
||||
" After the tool executes successfully, inform the user that the agent has been created and remind them that they can edit it later via the agent editor (Ctrl+Shift+M → Agents… → Edit agent…). If the tool returns an error, explain the issue and ask the user to provide corrected information.",
|
||||
"",
|
||||
"Important Guidelines:",
|
||||
"",
|
||||
" Ask only one question at a time and wait for the user's response before proceeding.",
|
||||
"",
|
||||
" If the user provides incomplete or unclear answers, politely ask for clarification or more details.",
|
||||
"",
|
||||
" Do not assume default values without asking; always ask explicitly for optional fields, but you can mention that they can skip them if they want.",
|
||||
"",
|
||||
" Keep your tone friendly and helpful. Make the process feel like a guided conversation.",
|
||||
"",
|
||||
" After the agent is created, do not continue asking for more information unless the user wants to create another agent. If they do, you may restart the process.",
|
||||
"",
|
||||
""
|
||||
],
|
||||
"tools": [
|
||||
"create_agent"
|
||||
]
|
||||
}
|
||||
]],
|
||||
[PREDEFINED_LISTS_KEYS.AGENT_COMMANDS, [
|
||||
|
|
|
|||
|
|
@ -5,14 +5,8 @@ import { Utils } from "./utils"
|
|||
import { Chat } from "./types"
|
||||
import { Plugin } from './plugin';
|
||||
import * as fs from 'fs';
|
||||
import { SUPPORTED_IMG_FILE_EXTS, UI_TEXT_KEYS } from "./constants";
|
||||
import path from "path";
|
||||
|
||||
|
||||
interface Frontmatter {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface Step {
|
||||
id: string | number;
|
||||
description: string;
|
||||
|
|
@ -28,8 +22,6 @@ export class LlamaAgent {
|
|||
private logText = ""
|
||||
public contexProjectFiles: Map<string,string> = new Map();
|
||||
public sentContextFiles: Map<string,string> = new Map();
|
||||
public contextImage: string = "";
|
||||
public sentContextImages: string[] = [];
|
||||
private abortController: AbortController | null = null;
|
||||
|
||||
constructor(application: Application) {
|
||||
|
|
@ -37,24 +29,9 @@ export class LlamaAgent {
|
|||
this.resetMessages();
|
||||
}
|
||||
|
||||
getAgentLogText = () => this.logText;
|
||||
|
||||
resetMessages = () => {
|
||||
let systemPromt = this.app.prompts.TOOLS_SYSTEM_PROMPT_ACTION;
|
||||
if (this.app.isAgentSelected()) systemPromt = this.app.getAgent().systemInstruction.join("\n")
|
||||
if (this.app.configuration.tool_delegate_task_enabled) {
|
||||
let agentPromtPrefix = " \n\n " + this.app.prompts.SUBAGENTS_DESCRIPTION;
|
||||
agentPromtPrefix += " \n\n Subagents:";
|
||||
let subagentsList = "";
|
||||
for (let agent of this.app.configuration.agents_list) {
|
||||
if (agent.subagentEnabled){
|
||||
subagentsList += " \n" + agent.name + ": " + agent.description;
|
||||
}
|
||||
}
|
||||
if (subagentsList.length > 0) {
|
||||
systemPromt += agentPromtPrefix + subagentsList;
|
||||
}
|
||||
}
|
||||
if (this.app.menu.isAgentSelected()) systemPromt = this.app.menu.getAgent().systemInstruction.join("\n")
|
||||
let worspaceFolder = "";
|
||||
if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders[0]){
|
||||
worspaceFolder = " Project root folder: " + vscode.workspace.workspaceFolders[0].uri.fsPath;
|
||||
|
|
@ -69,21 +46,7 @@ export class LlamaAgent {
|
|||
}
|
||||
} else {
|
||||
const absolutePath = Utils.getAbsolutFilePath("llama-vscode-rules.md");
|
||||
if (fs.existsSync(absolutePath)) {
|
||||
projectContext += " \n\nAdditional rules from the user: \n" + fs.readFileSync(absolutePath, "utf-8");
|
||||
}
|
||||
}
|
||||
const agentsAbsolutePath = Utils.getAbsolutFilePath("AGENTS.md");
|
||||
if (fs.existsSync(agentsAbsolutePath)) {
|
||||
projectContext += " \n\nInstructions from " + agentsAbsolutePath + ": \n" + fs.readFileSync(agentsAbsolutePath, "utf-8");
|
||||
}
|
||||
const soulAbsolutePath = Utils.getAbsolutFilePath("SOUL.md");
|
||||
if (fs.existsSync(soulAbsolutePath)) {
|
||||
projectContext += " \n\n AI soul desription from " + soulAbsolutePath + ": \n" + fs.readFileSync(soulAbsolutePath, "utf-8");
|
||||
}
|
||||
const userInstructionsPath = Utils.getAbsolutFilePath("USER.md");
|
||||
if (fs.existsSync(userInstructionsPath)) {
|
||||
projectContext += " \n\nUser profile from " + userInstructionsPath + ": \n" + fs.readFileSync(userInstructionsPath, "utf-8");
|
||||
if (fs.existsSync(absolutePath)) projectContext += " \n\nAdditional rules from the user: \n" + fs.readFileSync(absolutePath, "utf-8");
|
||||
}
|
||||
this.messages = [
|
||||
{
|
||||
|
|
@ -94,62 +57,29 @@ export class LlamaAgent {
|
|||
this.logText = "";
|
||||
}
|
||||
|
||||
selectChat = async (chat: Chat) => {
|
||||
if (chat && chat.defaultAgent) await this.app.agentService.selectAgent(chat.defaultAgent);
|
||||
selectChat = (chat: Chat) => {
|
||||
if (chat && chat.defaultAgent) this.app.menu.selectAgent(chat.defaultAgent);
|
||||
this.resetMessages();
|
||||
|
||||
if (chat){
|
||||
const currentChat = this.app.getChat();
|
||||
const currentChat = this.app.menu.getChat();
|
||||
this.messages = chat.messages??[];
|
||||
this.logText = chat.log??"";
|
||||
}
|
||||
// this.app.llamaWebviewProvider.logInUi(this.logText);
|
||||
this.resetContext();
|
||||
}
|
||||
this.app.llamaWebviewProvider.logInUi(this.logText);
|
||||
this.resetContextProjectFiles();
|
||||
}
|
||||
|
||||
resetContext = () => {
|
||||
resetContextProjectFiles = () => {
|
||||
this.contexProjectFiles.clear();
|
||||
this.app.llamaWebviewProvider.updateContextFilesInfo();
|
||||
this.sentContextFiles.clear();
|
||||
this.contextImage = "";
|
||||
this.sentContextImages = [];
|
||||
}
|
||||
|
||||
addContextProjectFile = (fileLongName: string, fileShortName: string) => {
|
||||
this.contexProjectFiles.set(fileLongName, fileShortName);
|
||||
}
|
||||
|
||||
addContextProjectImage = (imagePath: string) => {
|
||||
this.contextImage = imagePath;
|
||||
}
|
||||
|
||||
removeContextProjectImage = () => {
|
||||
this.contextImage = "";
|
||||
}
|
||||
|
||||
selectImageFile = async (): Promise<string> => {
|
||||
var imgPath = "";
|
||||
|
||||
var fileTypes = Object.values(SUPPORTED_IMG_FILE_EXTS)
|
||||
fileTypes = fileTypes.map(type => type.replace("image/", ""))
|
||||
|
||||
const uris = await vscode.window.showOpenDialog({
|
||||
canSelectMany: false,
|
||||
openLabel: 'Import Model',
|
||||
filters: {
|
||||
'Image Files': fileTypes
|
||||
},
|
||||
});
|
||||
|
||||
if (!uris || uris.length === 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
imgPath = uris[0].fsPath;
|
||||
|
||||
return imgPath;
|
||||
}
|
||||
|
||||
removeContextProjectFile = (fileLongName: string) => {
|
||||
this.contexProjectFiles.delete(fileLongName);
|
||||
}
|
||||
|
|
@ -158,10 +88,6 @@ export class LlamaAgent {
|
|||
return this.contexProjectFiles;
|
||||
}
|
||||
|
||||
getContextProjecImage = () => {
|
||||
return this.contextImage;
|
||||
}
|
||||
|
||||
run = async (query:string, agentCommand?:string) => {
|
||||
|
||||
await this.askAgent(query, agentCommand);
|
||||
|
|
@ -212,14 +138,13 @@ export class LlamaAgent {
|
|||
|
||||
askAgent = async (query:string, agentCommand?:string): Promise<string> => {
|
||||
let response = ""
|
||||
const originalQuery = query;
|
||||
let toolCallsResult: ChatMessage;
|
||||
let finishReason:string|undefined = "tool_calls"
|
||||
this.logText += "***" + query.split(/\r?\n/).join(" \n") + "***" + "\n\n"; // Make sure markdown shows new lines correctly
|
||||
this.logText += "***" + query.replace("\n", " \n") + "***" + "\n\n"; // Make sure markdown shows new lines correctly
|
||||
|
||||
|
||||
if (!this.app.isToolsModelSelected() && !this.app.configuration.endpoint_tools) {
|
||||
vscode.window.showErrorMessage("Error: Tools model is not selected! Select tools model (or env with tools model) or set and endpoint in setting endpoint_tools if you want to to use Llama Agent View.")
|
||||
if (!this.app.menu.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"
|
||||
}
|
||||
|
|
@ -229,11 +154,6 @@ export class LlamaAgent {
|
|||
this.summarize();
|
||||
}
|
||||
|
||||
// Get the skills
|
||||
const skillsFolder = this.app.configuration.skills_folder || Utils.getWorkspaceFolder() + "/" + "skills"
|
||||
let skillsDesc = this.getSkillsDesc(skillsFolder)
|
||||
if (skillsDesc) query += "\n\n" + skillsDesc
|
||||
|
||||
if (this.contexProjectFiles.size > 0){
|
||||
query += "\n\nBelow is a context, attached by the user.\n"
|
||||
for (const [key, value] of this.contexProjectFiles) {
|
||||
|
|
@ -252,13 +172,6 @@ export class LlamaAgent {
|
|||
this.sentContextFiles.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
const todoFile = Utils.getTodosFilePath()
|
||||
this.removeFile(todoFile);
|
||||
|
||||
if (this.app.configuration.tool_update_todo_list_enabled){
|
||||
query += "\n\n " + "If the request is complicated or involves multiple steps - use tool update_todo_list."
|
||||
}
|
||||
|
||||
if (agentCommand) {
|
||||
const commands = this.app.configuration.agent_commands as AgentCommand[];
|
||||
|
|
@ -292,21 +205,14 @@ export class LlamaAgent {
|
|||
this.resetMessages();
|
||||
return "agent stopped"
|
||||
}
|
||||
iterationsCount++;
|
||||
iterationsCount++;
|
||||
try {
|
||||
if (fs.existsSync(todoFile) && iterationsCount % this.app.configuration.plan_review_frequency == 0){
|
||||
let goal = "Task: \n" + originalQuery
|
||||
let currentPlan = "Below is the todo list:\n"
|
||||
currentPlan += fs.readFileSync(todoFile, "utf-8")
|
||||
this.messages.push({"role": "user", "content": goal + "\n\n" + currentPlan})
|
||||
}
|
||||
let streamed = "";
|
||||
let data:any = await this.app.llamaServer.getAgentCompletion(this.messages, false, (delta: string) => {
|
||||
streamed += delta;
|
||||
this.logText += delta;
|
||||
this.app.llamaWebviewProvider.logInUi(this.logText);
|
||||
}, this.abortController?.signal, !this.sentContextImages.includes(this.contextImage)? this.contextImage : "");
|
||||
if (this.contextImage) this.sentContextImages.push(this.contextImage)
|
||||
}, this.abortController?.signal);
|
||||
if (!data) {
|
||||
this.logText += "No response from AI" + " \n"
|
||||
this.app.llamaWebviewProvider.logInUi(this.logText);
|
||||
|
|
@ -355,13 +261,7 @@ export class LlamaAgent {
|
|||
const toolFunc = this.app.tools.toolsFunc.get(oneToolCall.function.name);
|
||||
if (toolFunc) {
|
||||
commandOutput = await toolFunc(oneToolCall.function.arguments);
|
||||
if (oneToolCall.function.name == "edit_file" && commandOutput != Utils.MSG_NO_UESR_PERMISSION) {
|
||||
changedFiles.add(commandDescription);
|
||||
if (commandOutput != UI_TEXT_KEYS.fileUpdated){
|
||||
this.logText += commandOutput + "\n\n"
|
||||
this.app.llamaWebviewProvider.logInUi(this.logText);
|
||||
}
|
||||
}
|
||||
if (oneToolCall.function.name == "edit_file" && commandOutput != Utils.MSG_NO_UESR_PERMISSION) changedFiles.add(commandDescription);
|
||||
if (oneToolCall.function.name == "delete_file" && commandOutput != Utils.MSG_NO_UESR_PERMISSION) deletedFiles.add(commandDescription);
|
||||
}
|
||||
}
|
||||
|
|
@ -403,12 +303,18 @@ export class LlamaAgent {
|
|||
this.logText += " \nAgent session finished. \n\n"
|
||||
this.app.llamaWebviewProvider.logInUi(this.logText);
|
||||
this.app.llamaWebviewProvider.setState("AI finished")
|
||||
await this.updateChat();
|
||||
let chat = this.app.menu.getChat()
|
||||
if (!this.app.menu.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)
|
||||
|
||||
// Clean up AbortController
|
||||
this.abortController = null;
|
||||
|
||||
this.removeFile(todoFile);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
|
@ -441,24 +347,6 @@ export class LlamaAgent {
|
|||
return progress;
|
||||
}
|
||||
|
||||
public async updateChat() {
|
||||
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.chatService.selectUpdateChat(chat);
|
||||
}
|
||||
|
||||
private removeFile(todoFile: string) {
|
||||
if (fs.existsSync(todoFile)) {
|
||||
fs.unlinkSync(todoFile);
|
||||
}
|
||||
}
|
||||
|
||||
private async getItemContext(key: string, value: string) {
|
||||
let itemContext = "";
|
||||
const document = await vscode.workspace.openTextDocument(vscode.Uri.file(key.split("|")[0]));
|
||||
|
|
@ -473,72 +361,4 @@ export class LlamaAgent {
|
|||
}
|
||||
return itemContext;
|
||||
}
|
||||
|
||||
private getSkillsDesc(skillsFolder: string): string {
|
||||
let desc = ""
|
||||
if (fs.existsSync(skillsFolder)) {
|
||||
desc += "<available_skills>"
|
||||
const items = fs.readdirSync(skillsFolder, { withFileTypes: true });
|
||||
|
||||
const folders = items
|
||||
.filter(item => item.isDirectory())
|
||||
.map(item => item.name);
|
||||
|
||||
for(let folder in folders){
|
||||
const skillsFile = path.join(skillsFolder, folders[folder], "SKILL.md");
|
||||
if (fs.existsSync(skillsFile)){
|
||||
desc += "<skill>"
|
||||
const frontMatter = this.parseFrontmatter(skillsFile)
|
||||
desc += `<name>${frontMatter.name}</name>`
|
||||
desc += `<description>${frontMatter.description}</description>`
|
||||
desc += `<location>${skillsFile}</location>`
|
||||
desc += "</skill>"
|
||||
}
|
||||
}
|
||||
desc += "</available_skills>"
|
||||
}
|
||||
return desc;
|
||||
}
|
||||
|
||||
private parseFrontmatter(filePath: string): Frontmatter {
|
||||
try {
|
||||
const fileContent = fs.readFileSync(filePath, 'utf-8');
|
||||
|
||||
// Match frontmatter between --- delimiters
|
||||
const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n?/;
|
||||
const match = fileContent.match(frontmatterRegex);
|
||||
|
||||
if (!match) {
|
||||
return { frontmatter: {}, content: fileContent };
|
||||
}
|
||||
|
||||
const frontmatterText = match[1];
|
||||
const content = fileContent.slice(match[0].length);
|
||||
|
||||
// Parse frontmatter (assuming YAML format)
|
||||
const frontmatter: Frontmatter = {};
|
||||
const lines = frontmatterText.split('\n');
|
||||
|
||||
for (const line of lines) {
|
||||
const colonIndex = line.indexOf(':');
|
||||
if (colonIndex > 0) {
|
||||
const key = line.slice(0, colonIndex).trim();
|
||||
const value = line.slice(colonIndex + 1).trim();
|
||||
|
||||
// Try to parse as JSON-like values
|
||||
try {
|
||||
frontmatter[key] = JSON.parse(value);
|
||||
} catch {
|
||||
// Remove quotes if present
|
||||
frontmatter[key] = value.replace(/^['"](.*)['"]$/, '$1');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return frontmatter;
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(`Failed to read or parse file: ${error}`);
|
||||
return {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,237 +0,0 @@
|
|||
import * as vscode from 'vscode';
|
||||
import axios from 'axios';
|
||||
import { Application } from './application';
|
||||
import { Utils } from './utils';
|
||||
|
||||
const VENDOR = 'llama-vscode';
|
||||
|
||||
// Default token limits used when the server does not report them
|
||||
const DEFAULT_MAX_INPUT_TOKENS = 8192;
|
||||
const DEFAULT_MAX_OUTPUT_TOKENS = 4096;
|
||||
|
||||
interface OpenAIModel {
|
||||
id: string;
|
||||
object?: string;
|
||||
}
|
||||
|
||||
interface OpenAIModelsResponse {
|
||||
data: OpenAIModel[];
|
||||
}
|
||||
|
||||
export class LlamaChatModelProvider implements vscode.LanguageModelChatProvider {
|
||||
private readonly _onDidChangeLanguageModelChatInformation = new vscode.EventEmitter<void>();
|
||||
readonly onDidChangeLanguageModelChatInformation: vscode.Event<void> =
|
||||
this._onDidChangeLanguageModelChatInformation.event;
|
||||
|
||||
constructor(private readonly app: Application) {}
|
||||
|
||||
/** Called by the configuration change handler to notify VS Code that models may have changed. */
|
||||
notifyModelsChanged(): void {
|
||||
this._onDidChangeLanguageModelChatInformation.fire();
|
||||
}
|
||||
|
||||
async provideLanguageModelChatInformation(
|
||||
_options: vscode.PrepareLanguageModelChatModelOptions,
|
||||
_token: vscode.CancellationToken
|
||||
): Promise<vscode.LanguageModelChatInformation[]> {
|
||||
const endpoint = this.getChatEndpoint();
|
||||
if (!endpoint) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const requestConfig = this.app.configuration.axiosRequestConfigChat;
|
||||
const response = await axios.get<OpenAIModelsResponse>(
|
||||
`${Utils.trimTrailingSlash(endpoint)}/${this.app.configuration.ai_api_version}/models`,
|
||||
requestConfig
|
||||
);
|
||||
|
||||
if (!response.data?.data?.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return response.data.data.map((model) => ({
|
||||
id: model.id,
|
||||
name: model.id,
|
||||
family: VENDOR,
|
||||
version: '1',
|
||||
maxInputTokens: DEFAULT_MAX_INPUT_TOKENS,
|
||||
maxOutputTokens: DEFAULT_MAX_OUTPUT_TOKENS,
|
||||
capabilities: {
|
||||
toolCalling: true,
|
||||
imageInput: false,
|
||||
},
|
||||
}));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async provideLanguageModelChatResponse(
|
||||
model: vscode.LanguageModelChatInformation,
|
||||
messages: readonly vscode.LanguageModelChatRequestMessage[],
|
||||
options: vscode.ProvideLanguageModelChatResponseOptions,
|
||||
progress: vscode.Progress<vscode.LanguageModelResponsePart>,
|
||||
token: vscode.CancellationToken
|
||||
): Promise<void> {
|
||||
const endpoint = this.getChatEndpoint();
|
||||
if (!endpoint) {
|
||||
throw new Error('No chat endpoint configured');
|
||||
}
|
||||
|
||||
const openaiMessages = messages.map((msg) => ({
|
||||
role: msg.role === vscode.LanguageModelChatMessageRole.User ? 'user' : 'assistant',
|
||||
content: msg.content
|
||||
.map((part: unknown) =>
|
||||
part instanceof vscode.LanguageModelTextPart ? part.value : ''
|
||||
)
|
||||
.join(''),
|
||||
}));
|
||||
|
||||
const tools = options.tools?.map((t: vscode.LanguageModelToolInformation) => ({
|
||||
type: 'function',
|
||||
function: {
|
||||
name: t.name,
|
||||
description: t.description,
|
||||
parameters: t.inputSchema,
|
||||
},
|
||||
}));
|
||||
|
||||
const requestBody: Record<string, unknown> = {
|
||||
model: model.id,
|
||||
messages: openaiMessages,
|
||||
stream: true,
|
||||
max_tokens: DEFAULT_MAX_OUTPUT_TOKENS,
|
||||
...(options.modelOptions?.temperature !== undefined && {
|
||||
temperature: options.modelOptions.temperature,
|
||||
}),
|
||||
...(tools?.length && { tools }),
|
||||
};
|
||||
|
||||
const abortController = new AbortController();
|
||||
token.onCancellationRequested(() => abortController.abort());
|
||||
|
||||
const requestConfig = this.app.configuration.axiosRequestConfigTools;
|
||||
const streamResponse = await axios.post<NodeJS.ReadableStream>(
|
||||
`${Utils.trimTrailingSlash(endpoint)}/${this.app.configuration.ai_api_version}/chat/completions`,
|
||||
requestBody,
|
||||
{ ...requestConfig, responseType: 'stream' as const, signal: abortController.signal }
|
||||
);
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const readable = streamResponse.data;
|
||||
let buffer = '';
|
||||
// Accumulated tool call data indexed by call index
|
||||
const toolCalls: { id: string; name: string; arguments: string }[] = [];
|
||||
|
||||
const finalize = () => {
|
||||
// Emit any completed tool calls that weren't emitted yet
|
||||
for (const tc of toolCalls) {
|
||||
if (tc.id && tc.name) {
|
||||
try {
|
||||
progress.report(
|
||||
new vscode.LanguageModelToolCallPart(tc.id, tc.name, JSON.parse(tc.arguments || '{}'))
|
||||
);
|
||||
} catch (e) {
|
||||
console.warn('[llama-vscode] Failed to parse tool call arguments:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
resolve();
|
||||
};
|
||||
|
||||
token.onCancellationRequested(() => {
|
||||
(readable as any).destroy?.();
|
||||
resolve();
|
||||
});
|
||||
|
||||
readable.on('data', (chunk: Buffer) => {
|
||||
buffer += chunk.toString('utf8');
|
||||
const lines = buffer.split(/\r?\n/);
|
||||
buffer = lines.pop() ?? '';
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || !trimmed.startsWith('data:')) {
|
||||
continue;
|
||||
}
|
||||
const payload = trimmed.slice(5).trim();
|
||||
if (payload === '[DONE]') {
|
||||
finalize();
|
||||
readable.removeAllListeners();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const json = JSON.parse(payload);
|
||||
const choice = json.choices?.[0];
|
||||
if (!choice) {
|
||||
continue;
|
||||
}
|
||||
const delta = choice.delta ?? {};
|
||||
if (typeof delta.content === 'string' && delta.content) {
|
||||
progress.report(new vscode.LanguageModelTextPart(delta.content));
|
||||
}
|
||||
if (Array.isArray(delta.tool_calls)) {
|
||||
for (const tc of delta.tool_calls) {
|
||||
const idx: number = typeof tc.index === 'number' ? tc.index : 0;
|
||||
if (!toolCalls[idx]) {
|
||||
toolCalls[idx] = { id: '', name: '', arguments: '' };
|
||||
}
|
||||
if (tc.id) {
|
||||
toolCalls[idx].id = tc.id;
|
||||
}
|
||||
if (tc.function?.name) {
|
||||
toolCalls[idx].name = tc.function.name;
|
||||
}
|
||||
if (tc.function?.arguments) {
|
||||
toolCalls[idx].arguments += tc.function.arguments;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Skip malformed SSE chunks
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
readable.on('end', () => {
|
||||
finalize();
|
||||
});
|
||||
|
||||
readable.on('error', (err: Error) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
provideTokenCount(
|
||||
_model: vscode.LanguageModelChatInformation,
|
||||
text: string | vscode.LanguageModelChatRequestMessage,
|
||||
_token: vscode.CancellationToken
|
||||
): Thenable<number> {
|
||||
const content =
|
||||
typeof text === 'string'
|
||||
? text
|
||||
: text.content
|
||||
.map((p: unknown) => (p instanceof vscode.LanguageModelTextPart ? p.value : ''))
|
||||
.join('');
|
||||
// Rough approximation: 1 token ≈ 4 characters. The llama.cpp server does not expose a
|
||||
// tokenization endpoint via the standard OpenAI API, so we use this heuristic.
|
||||
// Actual token counts may differ depending on the model's tokenizer.
|
||||
return Promise.resolve(Math.ceil(content.length / 4));
|
||||
}
|
||||
|
||||
private getChatEndpoint(): string {
|
||||
const selectedModel = this.app.getToolsModel();
|
||||
if (selectedModel?.endpoint) {
|
||||
return selectedModel.endpoint;
|
||||
}
|
||||
if (this.app.configuration.endpoint_chat) {
|
||||
return this.app.configuration.endpoint_chat;
|
||||
}
|
||||
if (this.app.configuration.endpoint_tools) {
|
||||
return this.app.configuration.endpoint_tools;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,10 @@
|
|||
import axios, { AxiosRequestConfig } from "axios";
|
||||
import axios from "axios";
|
||||
import {Application} from "./application";
|
||||
import vscode, { Terminal } from "vscode";
|
||||
import { LlmModel, LlamaChatResponse, LlamaResponse, ChatMessage } from "./types";
|
||||
import { Utils } from "./utils";
|
||||
import * as cp from 'child_process';
|
||||
import * as util from 'util';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { ModelType, SUPPORTED_IMG_FILE_EXTS } from "./constants";
|
||||
|
||||
const STATUS_OK = 200;
|
||||
|
||||
|
|
@ -15,6 +12,7 @@ export interface LlamaToolsResponse {
|
|||
choices: [{
|
||||
message:{content?: string, tool_calls?:[{id:string, function: {name:string, arguments: string}}]},
|
||||
finish_reason?: string,
|
||||
|
||||
}];
|
||||
}
|
||||
|
||||
|
|
@ -108,7 +106,6 @@ export class LlamaServer {
|
|||
private createRequestPayload(noPredict: boolean, inputPrefix: string, inputSuffix: string, chunks: any[], prompt: string, model: string, nindent?: number) {
|
||||
if (noPredict) {
|
||||
return {
|
||||
id_slot: 0,
|
||||
input_prefix: inputPrefix,
|
||||
input_suffix: inputSuffix,
|
||||
input_extra: chunks,
|
||||
|
|
@ -123,13 +120,11 @@ export class LlamaServer {
|
|||
}
|
||||
|
||||
return {
|
||||
id_slot: 0,
|
||||
input_prefix: inputPrefix,
|
||||
input_suffix: inputSuffix,
|
||||
input_extra: chunks,
|
||||
prompt,
|
||||
n_predict: this.app.configuration.n_predict,
|
||||
n_cmpl: this.app.configuration.max_parallel_completions,
|
||||
...this.defaultRequestParams,
|
||||
...(nindent && { n_indent: nindent }),
|
||||
t_max_prompt_ms: this.app.configuration.t_max_prompt_ms,
|
||||
|
|
@ -237,60 +232,26 @@ export class LlamaServer {
|
|||
};
|
||||
}
|
||||
|
||||
private createToolsRequestPayload(messages: ChatMessage[], model: string, stream = false, imagePath: string = "") {
|
||||
private createToolsRequestPayload(messages: ChatMessage[], model: string, stream = false) {
|
||||
this.app.tools.addSelectedTools();
|
||||
let filteredMsgs = this.filterThoughtFromMsgs(messages)
|
||||
|
||||
// Add image with base64 encoding
|
||||
if (imagePath && fs.existsSync(imagePath)) {
|
||||
|
||||
var imgType = ""
|
||||
for (var suffix in SUPPORTED_IMG_FILE_EXTS){
|
||||
if (imagePath.endsWith(suffix)) {
|
||||
imgType = SUPPORTED_IMG_FILE_EXTS[suffix];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (imgType) {
|
||||
const imageBuffer = fs.readFileSync(imagePath);
|
||||
const base64Image = imageBuffer.toString('base64');
|
||||
const imageMessage = {
|
||||
role: 'user',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Here is an image for context:'
|
||||
},
|
||||
{
|
||||
type: 'image_url',
|
||||
image_url: {
|
||||
url: `data:${imgType};base64,${base64Image}`
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
filteredMsgs = (filteredMsgs as any[])
|
||||
filteredMsgs.push(imageMessage);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
return {
|
||||
"messages": filteredMsgs,
|
||||
"stream": stream,
|
||||
"temperature": 0.8,
|
||||
"top_p": 0.95,
|
||||
...(model.trim() != "" && { model: model}),
|
||||
"tools": [...this.app.tools.getTools(), ...this.app.tools.vscodeTools],
|
||||
"tools": [...this.app.tools.tools, ...this.app.tools.vscodeTools],
|
||||
"tool_choice": "auto"
|
||||
};
|
||||
}
|
||||
|
||||
private createGetSummaryRequestPayload(messages: ChatMessage[], model: string) {
|
||||
private createGetSummaryRequestPayload(messages: ChatMessage[], model: string) {
|
||||
let filteredMsgs = this.filterThoughtFromMsgs(messages)
|
||||
const summaryPromptMsgs: ChatMessage[] = [
|
||||
{
|
||||
role: 'system',
|
||||
content: `Summarize the conversation concisely, preserving technical details and code solutions.`
|
||||
role: 'system',
|
||||
content: `Summarize the conversation concisely, preserving technical details and code solutions.`
|
||||
},
|
||||
...filteredMsgs
|
||||
];
|
||||
|
|
@ -318,11 +279,11 @@ private createGetSummaryRequestPayload(messages: ChatMessage[], model: string) {
|
|||
|
||||
// else, default to llama.cpp
|
||||
let { endpoint, model, requestConfig } = this.getComplModelProperties();
|
||||
if (!endpoint) {
|
||||
if (!endpoint) {
|
||||
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.llamaWebviewProvider.showEnvView();
|
||||
this.app.menu.showEnvView();
|
||||
vscode.window.showInformationMessage("After the completion model is loaded, try again using code completion.")
|
||||
return;
|
||||
} else {
|
||||
|
|
@ -352,7 +313,7 @@ private createGetSummaryRequestPayload(messages: ChatMessage[], model: string) {
|
|||
chunks: any,
|
||||
nindent: number
|
||||
): Promise<LlamaChatResponse | undefined> => {
|
||||
|
||||
|
||||
let { endpoint, model, requestConfig } = this.getChatModelProperties();
|
||||
|
||||
const response = await axios.post<LlamaChatResponse>(
|
||||
|
|
@ -382,16 +343,15 @@ private createGetSummaryRequestPayload(messages: ChatMessage[], model: string) {
|
|||
messages: ChatMessage[],
|
||||
isSummarization = false,
|
||||
onDelta?: (delta: string) => void,
|
||||
abortSignal?: AbortSignal,
|
||||
imagePath = ""
|
||||
abortSignal?: AbortSignal
|
||||
): Promise<LlamaToolsResponse | undefined> => {
|
||||
let selectedModel: LlmModel = this.app.getToolsModel();
|
||||
let selectedModel: LlmModel = this.app.menu.getToolsModel();
|
||||
let model = this.app.configuration.ai_model;
|
||||
if (selectedModel?.aiModel !== undefined && selectedModel.aiModel) model = selectedModel.aiModel;
|
||||
|
||||
let endpoint = this.app.configuration.endpoint_tools;
|
||||
if (selectedModel?.endpoint !== undefined && selectedModel.endpoint) endpoint = selectedModel.endpoint;
|
||||
|
||||
|
||||
let requestConfig = this.app.configuration.axiosRequestConfigTools;
|
||||
if (selectedModel?.isKeyRequired !== undefined && selectedModel.isKeyRequired){
|
||||
const apiKey = this.app.persistence.getApiKey(selectedModel.endpoint??"");
|
||||
|
|
@ -404,10 +364,10 @@ private createGetSummaryRequestPayload(messages: ChatMessage[], model: string) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let uri = `${Utils.trimTrailingSlash(endpoint)}/${this.app.configuration.ai_api_version}/chat/completions`;
|
||||
let request: any;
|
||||
|
||||
|
||||
if (isSummarization) {
|
||||
request = this.createGetSummaryRequestPayload(messages, model);
|
||||
const response = await axios.post<LlamaToolsResponse>(
|
||||
|
|
@ -419,7 +379,7 @@ private createGetSummaryRequestPayload(messages: ChatMessage[], model: string) {
|
|||
}
|
||||
|
||||
// Streaming branch for tools/agent calls
|
||||
request = this.createToolsRequestPayload(messages, model, true, imagePath);
|
||||
request = this.createToolsRequestPayload(messages, model, true);
|
||||
|
||||
try {
|
||||
const streamResponse = await axios.post<any>(
|
||||
|
|
@ -519,7 +479,7 @@ private createGetSummaryRequestPayload(messages: ChatMessage[], model: string) {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
updateExtraContext = (chunks: any[]): void => {
|
||||
// If the server is OpenAI compatible, use the OpenAI API to prepare for the next FIM
|
||||
|
|
@ -538,13 +498,13 @@ private createGetSummaryRequestPayload(messages: ChatMessage[], model: string) {
|
|||
|
||||
getEmbeddings = async (text: string): Promise<LlamaEmbeddingsResponse | undefined> => {
|
||||
try {
|
||||
let selectedModel: LlmModel = this.app.getEmbeddingsModel();
|
||||
let selectedModel: LlmModel = this.app.menu.getEmbeddingsModel();
|
||||
let model = this.app.configuration.ai_model;
|
||||
if (selectedModel.aiModel) model = selectedModel.aiModel;
|
||||
|
||||
let endpoint = this.app.configuration.endpoint_embeddings;
|
||||
if (selectedModel.endpoint) endpoint = selectedModel.endpoint;
|
||||
|
||||
|
||||
let requestConfig = this.app.configuration.axiosRequestConfigEmbeddings;
|
||||
if (selectedModel.isKeyRequired){
|
||||
const apiKey = this.app.persistence.getApiKey(selectedModel.endpoint??"");
|
||||
|
|
@ -557,7 +517,7 @@ private createGetSummaryRequestPayload(messages: ChatMessage[], model: string) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const response = await axios.post<LlamaEmbeddingsResponse>(
|
||||
`${Utils.trimTrailingSlash(endpoint)}/v1/embeddings`,
|
||||
{
|
||||
|
|
@ -698,7 +658,7 @@ private createGetSummaryRequestPayload(messages: ChatMessage[], model: string) {
|
|||
name: 'llama-vscode Command Terminal'
|
||||
});
|
||||
// }
|
||||
|
||||
|
||||
this.vsCodeCommandTerminal.show(true);
|
||||
this.vsCodeCommandTerminal.sendText(`echo "Executing: ${command}"`);
|
||||
try {
|
||||
|
|
@ -707,7 +667,7 @@ private createGetSummaryRequestPayload(messages: ChatMessage[], model: string) {
|
|||
// Show output in terminal
|
||||
this.vsCodeCommandTerminal.sendText(`echo "Command completed successfully"`);
|
||||
this.vsCodeCommandTerminal.sendText(`echo "Output: ${stdout.trim()}"`);
|
||||
|
||||
|
||||
return { stdout, stderr };
|
||||
} catch (error: any) {
|
||||
this.vsCodeCommandTerminal.sendText(`echo "Command failed: ${error.message}"`);
|
||||
|
|
@ -772,7 +732,7 @@ private createGetSummaryRequestPayload(messages: ChatMessage[], model: string) {
|
|||
this.vsCodeCommandTerminal = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
killToolsCmd = (): void => {
|
||||
if (this.vsCodeToolsTerminal) {
|
||||
this.vsCodeToolsTerminal.dispose();
|
||||
|
|
@ -782,37 +742,33 @@ private createGetSummaryRequestPayload(messages: ChatMessage[], model: string) {
|
|||
|
||||
|
||||
private getChatModelProperties() {
|
||||
let selectedModel: LlmModel = this.app.getChatModel();
|
||||
if (!this.app.isChatModelSelected() && !this.app.configuration.endpoint_chat) selectedModel = this.app.getToolsModel();
|
||||
let selectedModel: LlmModel = this.app.menu.getChatModel();
|
||||
if (!this.app.menu.isChatModelSelected()) selectedModel = this.app.menu.getToolsModel();
|
||||
|
||||
let model = this.app.configuration.ai_model;
|
||||
if (selectedModel?.aiModel !== undefined && selectedModel.aiModel) model = selectedModel.aiModel;
|
||||
|
||||
let endpoint = this.app.configuration.endpoint_chat;
|
||||
let model = this.app.configuration.ai_model;
|
||||
if (!endpoint) endpoint = this.app.configuration.endpoint_tools;
|
||||
if (selectedModel?.endpoint !== undefined && selectedModel.endpoint) endpoint = selectedModel.endpoint;
|
||||
|
||||
let requestConfig = this.app.configuration.axiosRequestConfigChat;
|
||||
if (!endpoint) {
|
||||
endpoint = this.app.configuration.endpoint_tools;
|
||||
requestConfig = this.app.configuration.axiosRequestConfigTools;
|
||||
}
|
||||
if (selectedModel?.endpoint !== undefined && selectedModel.endpoint) {
|
||||
endpoint = selectedModel.endpoint;
|
||||
if (selectedModel?.aiModel !== undefined && selectedModel.aiModel) model = selectedModel.aiModel;
|
||||
if (selectedModel?.isKeyRequired !== undefined && selectedModel.isKeyRequired) {
|
||||
const apiKey = this.app.persistence.getApiKey(selectedModel.endpoint??"");
|
||||
if (apiKey) {
|
||||
requestConfig = {
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
};
|
||||
}
|
||||
if (selectedModel?.isKeyRequired !== undefined && selectedModel.isKeyRequired) {
|
||||
const apiKey = this.app.persistence.getApiKey(selectedModel.endpoint??"");
|
||||
if (apiKey) {
|
||||
requestConfig = {
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return { endpoint, model, requestConfig };
|
||||
}
|
||||
|
||||
private getComplModelProperties() {
|
||||
const selectedComplModel: LlmModel = this.app.getComplModel();
|
||||
const selectedComplModel: LlmModel = this.app.menu.getComplModel();
|
||||
let model = this.app.configuration.ai_model;
|
||||
if (selectedComplModel?.aiModel !== undefined && selectedComplModel.aiModel) model = selectedComplModel.aiModel;
|
||||
|
||||
|
|
@ -833,36 +789,4 @@ private createGetSummaryRequestPayload(messages: ChatMessage[], model: string) {
|
|||
}
|
||||
return { endpoint, model, requestConfig };
|
||||
}
|
||||
|
||||
checkHealth = async (modelType: ModelType, model: LlmModel) => {
|
||||
let requestConfig: AxiosRequestConfig = this.app.configuration.axiosRequestConfigCompl;
|
||||
switch (modelType) {
|
||||
case ModelType.Chat:
|
||||
requestConfig = this.app.configuration.axiosRequestConfigChat;
|
||||
break;
|
||||
case ModelType.Completion:
|
||||
requestConfig = this.app.configuration.axiosRequestConfigCompl;
|
||||
break;
|
||||
case ModelType.Tools:
|
||||
requestConfig = this.app.configuration.axiosRequestConfigTools;
|
||||
break;
|
||||
case ModelType.Embeddings:
|
||||
requestConfig = this.app.configuration.axiosRequestConfigEmbeddings;
|
||||
break;
|
||||
}
|
||||
try {
|
||||
// TODO:Make sure to work with OpenRauter too
|
||||
let response = await axios.get(model.endpoint + "/health", requestConfig);
|
||||
if (!response.data.hasOwnProperty("status")) return "Error: No health status field found";
|
||||
return response.data.status
|
||||
} catch (error) {
|
||||
if (error instanceof TypeError) {
|
||||
return "TypeError occurred: " + error.message;
|
||||
} else if (error instanceof ReferenceError) {
|
||||
return "ReferenceError occurred:" + error.message;
|
||||
} else {
|
||||
return "An unexpected Error occurred:" + (error as Error).message;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,104 +52,68 @@ export class LlamaWebviewProvider implements vscode.WebviewViewProvider {
|
|||
this.app.llamaAgent.run(message.text, message.agentCommand);
|
||||
break;
|
||||
case 'clearText':
|
||||
await this.clearChatText(webviewView);
|
||||
this.app.llamaAgent.resetMessages();
|
||||
this.app.llamaAgent.resetContextProjectFiles()
|
||||
await this.app.menu.selectUpdateChat({name:"", id:""})
|
||||
vscode.commands.executeCommand('llama-vscode.webview.postMessage', {
|
||||
command: 'updateText',
|
||||
text: ''
|
||||
});
|
||||
break;
|
||||
case 'showChatsHistory':
|
||||
this.app.chatService.selectChatFromList();
|
||||
this.app.menu.selectChatFromList();
|
||||
break;
|
||||
case 'configureTools':
|
||||
await this.app.tools.selectTools()
|
||||
break;
|
||||
case 'configureEditTools':
|
||||
const selectedTools = await this.app.agentService.selectTools(message.tools)
|
||||
this.app.agentService.resetEditedAgentTools();
|
||||
selectedTools.map(toolName => this.app.agentService.addEditedAgentTools(toolName,""))
|
||||
let selAgentTools = this.app.agentService.getEditedAgentTools();
|
||||
webviewView.webview.postMessage({
|
||||
command: 'updateAgentTools',
|
||||
files: Array.from(selAgentTools.entries())
|
||||
});
|
||||
break;
|
||||
case 'stopSession':
|
||||
this.app.llamaAgent.stopAgent();
|
||||
break;
|
||||
case 'selectModelWithTools':
|
||||
await this.app.modelService.selectAndSetModel(ModelType.Tools, this.app.configuration.tools_models_list);
|
||||
await this.app.menu.selectAndSetModel(ModelType.Tools, this.app.configuration.tools_models_list);
|
||||
break;
|
||||
case 'selectModelForChat':
|
||||
await this.app.modelService.selectAndSetModel(ModelType.Chat, this.app.configuration.chat_models_list);
|
||||
await this.app.menu.selectAndSetModel(ModelType.Chat, this.app.configuration.chat_models_list);
|
||||
break;
|
||||
case 'selectModelForEmbeddings':
|
||||
await this.app.modelService.selectAndSetModel(ModelType.Embeddings, this.app.configuration.embeddings_models_list);
|
||||
await this.app.menu.selectAndSetModel(ModelType.Embeddings, this.app.configuration.embeddings_models_list);
|
||||
break;
|
||||
case 'selectModelForCompletion':
|
||||
await this.app.modelService.selectAndSetModel(ModelType.Completion, this.app.configuration.completion_models_list);
|
||||
await this.app.menu.selectAndSetModel(ModelType.Completion, this.app.configuration.completion_models_list);
|
||||
break;
|
||||
case 'deselectCompletionModel':
|
||||
await this.app.modelService.deselectAndClearModel(ModelType.Completion);
|
||||
break;
|
||||
case 'moreCompletionModel':
|
||||
await this.app.modelService.processModelActions(ModelType.Completion);
|
||||
break;
|
||||
case 'checkModelHealth':
|
||||
await this.app.modelService.checkModelHealth(message.model);
|
||||
break;
|
||||
case 'selectAgentModel':
|
||||
await this.app.modelService.selectAgentModel(ModelType.Tools, this.app.configuration.tools_models_list);
|
||||
break;
|
||||
case 'moreChatModel':
|
||||
await this.app.modelService.processModelActions(ModelType.Chat);
|
||||
break;
|
||||
case 'moreEmbeddingsModel':
|
||||
await this.app.modelService.processModelActions(ModelType.Embeddings);
|
||||
break;
|
||||
case 'moreToolsModel':
|
||||
await this.app.modelService.processModelActions(ModelType.Tools);
|
||||
break;
|
||||
case 'moreAgent':
|
||||
await this.app.agentService.processActions();
|
||||
await this.app.menu.deselectAndClearModel(ModelType.Completion);
|
||||
break;
|
||||
case 'deselectChatModel':
|
||||
await this.app.modelService.deselectAndClearModel(ModelType.Chat);
|
||||
await this.app.menu.deselectAndClearModel(ModelType.Chat);
|
||||
break;
|
||||
case 'deselectEmbsModel':
|
||||
await this.app.modelService.deselectAndClearModel(ModelType.Embeddings);
|
||||
await this.app.menu.deselectAndClearModel(ModelType.Embeddings);
|
||||
break;
|
||||
case 'deselectToolsModel':
|
||||
await this.app.modelService.deselectAndClearModel(ModelType.Tools);
|
||||
break;
|
||||
case 'deselectAgentModel':
|
||||
this.app.setAgentModel(undefined);
|
||||
this.updateLlamaView();
|
||||
await this.app.menu.deselectAndClearModel(ModelType.Tools);
|
||||
break;
|
||||
case 'deselectAgent':
|
||||
await this.app.agentService.deselectAgent();
|
||||
break;
|
||||
case 'selectEditAgent':
|
||||
await this.app.agentService.editAgent(this.app.configuration.agents_list)
|
||||
await this.app.menu.deselectAgent();
|
||||
break;
|
||||
case 'showCompletionModel':
|
||||
this.app.modelService.showModelDetails(this.app.getComplModel());
|
||||
this.app.modelService.showModelDetails(this.app.menu.getComplModel());
|
||||
break;
|
||||
case 'showChatModel':
|
||||
this.app.modelService.showModelDetails(this.app.getChatModel());
|
||||
this.app.modelService.showModelDetails(this.app.menu.getChatModel());
|
||||
break;
|
||||
case 'showEmbsModel':
|
||||
this.app.modelService.showModelDetails(this.app.getEmbeddingsModel());
|
||||
this.app.modelService.showModelDetails(this.app.menu.getEmbeddingsModel());
|
||||
break;
|
||||
case 'showToolsModel':
|
||||
this.app.modelService.showModelDetails(this.app.getToolsModel());
|
||||
this.app.modelService.showModelDetails(this.app.menu.getToolsModel());
|
||||
break;
|
||||
case 'showAgentDetails':
|
||||
this.app.agentService.showAgentDetails(this.app.getAgent())
|
||||
this.app.menu.showAgentDetails(this.app.menu.getAgent())
|
||||
break;
|
||||
case 'selectAgent':
|
||||
let agentsList = this.app.configuration.agents_list
|
||||
let shouldContinue = await Utils.showYesNoDialog("This will remove the current conversation. Do you want to continue?")
|
||||
if (shouldContinue) {
|
||||
await this.app.agentService.pickAndSelectAgent(agentsList)
|
||||
await this.clearChatText(webviewView);
|
||||
}
|
||||
await this.app.menu.selectAgentFromList(agentsList)
|
||||
break;
|
||||
case 'chatWithAI':
|
||||
this.app.askAi.closeChatWithAi(false);
|
||||
|
|
@ -162,22 +126,19 @@ export class LlamaWebviewProvider implements vscode.WebviewViewProvider {
|
|||
await this.app.modelService.addModel(ModelType.Chat, "hf");
|
||||
break;
|
||||
case 'selectEnv':
|
||||
await this.app.envService.selectEnv(this.app.configuration.envs_list, true);
|
||||
await this.app.menu.selectEnvFromList(this.app.configuration.envs_list);
|
||||
break;
|
||||
case 'stopEnv':
|
||||
await this.app.envService.stopEnv();
|
||||
await this.app.menu.stopEnv();
|
||||
break;
|
||||
case 'showEnvView':
|
||||
this.showEnvView();
|
||||
this.app.menu.showEnvView();
|
||||
break;
|
||||
case 'showAgentView':
|
||||
this.showAgentView();
|
||||
break;
|
||||
case 'showAgentEditor':
|
||||
this.showAgentEditor();
|
||||
this.app.menu.showAgentView();
|
||||
break;
|
||||
case 'showSelectedModels':
|
||||
await this.app.envService.showCurrentEnv();
|
||||
await this.app.menu.showCurrentEnv();
|
||||
break;
|
||||
case 'getFileList':
|
||||
let fileKeys: string[]
|
||||
|
|
@ -202,13 +163,6 @@ export class LlamaWebviewProvider implements vscode.WebviewViewProvider {
|
|||
files: agentCommands
|
||||
});
|
||||
break;
|
||||
case 'getAgentTools':
|
||||
let agentTools = this.app.tools.getTools().map(tool => tool.function.name + " | " + tool.function.description)
|
||||
webviewView.webview.postMessage({
|
||||
command: 'updateFileList',
|
||||
files: agentTools
|
||||
});
|
||||
break;
|
||||
case 'addContextProjectFile':
|
||||
let fileNames = message.fileLongName.split("|");
|
||||
this.app.llamaAgent.addContextProjectFile(fileNames[1].trim(),fileNames[0].trim());
|
||||
|
|
@ -226,101 +180,13 @@ export class LlamaWebviewProvider implements vscode.WebviewViewProvider {
|
|||
files: Array.from(updatedContextFiles.entries())
|
||||
});
|
||||
break;
|
||||
case 'selectImageFile':
|
||||
var selImgPath = await this.app.llamaAgent.selectImageFile();
|
||||
this.app.llamaAgent.addContextProjectImage(selImgPath)
|
||||
webviewView.webview.postMessage({
|
||||
command: 'updateContextImage',
|
||||
image: selImgPath
|
||||
});
|
||||
break;
|
||||
case 'addContextProjectImage':
|
||||
let imagePath = message.image;
|
||||
this.app.llamaAgent.addContextProjectImage(imagePath);
|
||||
const contextImage = this.app.llamaAgent.getContextProjecImage();
|
||||
webviewView.webview.postMessage({
|
||||
command: 'updateContextImage',
|
||||
image: contextImage
|
||||
});
|
||||
break;
|
||||
case 'removeContextProjectImage':
|
||||
this.app.llamaAgent.removeContextProjectImage();
|
||||
webviewView.webview.postMessage({
|
||||
command: 'updateContextImage',
|
||||
files: ""
|
||||
});
|
||||
break;
|
||||
case 'addEditedAgentTool':
|
||||
let toolsNames = message.fileLongName.split("|");
|
||||
this.app.agentService.addEditedAgentTools(toolsNames[0].trim(),toolsNames[1].trim());
|
||||
const editedAgentTools = this.app.agentService.getEditedAgentTools();
|
||||
webviewView.webview.postMessage({
|
||||
command: 'updateAgentTools',
|
||||
files: Array.from(editedAgentTools.entries())
|
||||
});
|
||||
break;
|
||||
case 'removeEditedAgentTool':
|
||||
this.app.agentService.removeEditedAgentTools(message.fileLongName);
|
||||
const updatedTools = this.app.agentService.getEditedAgentTools();
|
||||
webviewView.webview.postMessage({
|
||||
command: 'updateAgentTools',
|
||||
files: Array.from(updatedTools.entries())
|
||||
});
|
||||
break;
|
||||
case 'saveEditAgent':
|
||||
if (!message.name) {
|
||||
vscode.window.showErrorMessage("Agent should have a name!")
|
||||
return;
|
||||
}
|
||||
let agentModelToSave: LlmModel | undefined = undefined
|
||||
if (message.toolsModel) agentModelToSave = this.app.getTmpAgentModel();
|
||||
let agentToSave: Agent = {
|
||||
name: message.name,
|
||||
description: message.description,
|
||||
subagentEnabled: message.subagentEnabled,
|
||||
systemInstruction: message.systemInstruction.split(/\r?\n/),
|
||||
toolsModel: agentModelToSave,
|
||||
tools: message.tools
|
||||
}
|
||||
await this.app.agentService.addUpdateAgent(agentToSave)
|
||||
break;
|
||||
case 'refreshEditedAgentTool':
|
||||
const refreshedTols = this.app.agentService.getEditedAgentTools();
|
||||
webviewView.webview.postMessage({
|
||||
command: 'updateAgentTools',
|
||||
files: Array.from(refreshedTols.entries())
|
||||
});
|
||||
break;
|
||||
case 'editSelectedAgent':
|
||||
const selectedAgent = this.app.getAgent()
|
||||
this.addEditAgent(selectedAgent);
|
||||
break
|
||||
case 'addEditAgent':
|
||||
let toolsModel = Application.emptyModel
|
||||
if (message.toolsModel) toolsModel = this.app.getTmpAgentModel();
|
||||
const newAgent: Agent = {
|
||||
name: message.name,
|
||||
description: message.description,
|
||||
subagentEnabled: message.subagentEnabled,
|
||||
systemInstruction: message.systemInstruction,
|
||||
toolsModel: toolsModel,
|
||||
tools: message.tools
|
||||
}
|
||||
this.addEditAgent(newAgent);
|
||||
break
|
||||
case 'copyAsNewAgent':
|
||||
this.app.agentService.copyAgent()
|
||||
break;
|
||||
case 'deleteAgent':
|
||||
this.app.agentService.deleteAgent()
|
||||
break;
|
||||
case 'openContextFile':
|
||||
const uri = vscode.Uri.file(message.fileLongName);
|
||||
const document = await vscode.workspace.openTextDocument(uri);
|
||||
await vscode.window.showTextDocument(document);
|
||||
break;
|
||||
case 'addEnv':
|
||||
this.app.envService.addEnv(this.app.configuration.envs_list, SETTING_NAME_FOR_LIST.ENVS)
|
||||
this.app.menu.addEnvToList(this.app.configuration.envs_list, SETTING_NAME_FOR_LIST.ENVS)
|
||||
break;
|
||||
case 'toggleCompletionsEnabled':
|
||||
this.app.configuration.updateConfigValue("enabled", message.enabled)
|
||||
|
|
@ -355,40 +221,10 @@ export class LlamaWebviewProvider implements vscode.WebviewViewProvider {
|
|||
files: Array.from(contextFiles.entries())
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
private async clearChatText(webviewView: vscode.WebviewView) {
|
||||
this.app.llamaAgent.resetMessages();
|
||||
this.app.llamaAgent.resetContext();
|
||||
await this.app.chatService.selectUpdateChat({ name: "", id: "" });
|
||||
vscode.commands.executeCommand('llama-vscode.webview.postMessage', {
|
||||
command: 'updateText',
|
||||
text: ''
|
||||
});
|
||||
webviewView.webview.postMessage({
|
||||
command: 'updateContextImage',
|
||||
image: ""
|
||||
});
|
||||
}
|
||||
|
||||
public addEditAgent(agent: Agent) {
|
||||
this.app.agentService.resetEditedAgentTools();
|
||||
agent.tools?.map(tool => this.app.agentService.addEditedAgentTools(tool, ""));
|
||||
const edAgtools = this.app.agentService.getEditedAgentTools();
|
||||
this.app.setAgentModel(agent.toolsModel);
|
||||
vscode.commands.executeCommand('llama-vscode.webview.postMessage', {
|
||||
command: 'loadAgent',
|
||||
name: agent?.name,
|
||||
description: agent?.description,
|
||||
subagentEnabled: agent?.subagentEnabled,
|
||||
systemInstruction: agent?.systemInstruction.join("\n"),
|
||||
toolsModel: agent?.toolsModel?.name??'',
|
||||
tools: Array.from(edAgtools.entries())
|
||||
});
|
||||
}
|
||||
|
||||
private updateSettingInEnvView(key: string, settingValue: any) {
|
||||
vscode.commands.executeCommand('llama-vscode.webview.postMessage', {
|
||||
vscode.commands.executeCommand('llama-vscode.webview.postMessage', {
|
||||
command: 'vscodeSettingValue',
|
||||
key: key,
|
||||
value: settingValue
|
||||
|
|
@ -399,66 +235,42 @@ export class LlamaWebviewProvider implements vscode.WebviewViewProvider {
|
|||
this.updateSettingInEnvView('enabled', this.app.configuration.enabled);
|
||||
this.updateSettingInEnvView('rag_enabled', this.app.configuration.rag_enabled);
|
||||
this.updateSettingInEnvView('env_start_last_used', this.app.configuration.env_start_last_used);
|
||||
this.updateSettingInEnvView('health_check_compl_enabled', this.app.configuration.health_check_compl_enabled);
|
||||
this.updateSettingInEnvView('health_check_chat_enabled', this.app.configuration.health_check_chat_enabled);
|
||||
this.updateSettingInEnvView('health_check_embs_enabled', this.app.configuration.health_check_embs_enabled);
|
||||
this.updateSettingInEnvView('health_check_tools_enabled', this.app.configuration.health_check_tools_enabled);
|
||||
}
|
||||
|
||||
private updateEmbsModel(status: string = "") {
|
||||
const currentEmbeddingsModel: LlmModel = this.app.getEmbeddingsModel();
|
||||
let modelName = currentEmbeddingsModel.name
|
||||
if (this.app.configuration.health_check_embs_enabled && status && status.toLowerCase() != "ok")
|
||||
modelName += ": " + status;
|
||||
private updateEmbsModel() {
|
||||
const currentEmbeddingsModel: LlmModel = this.app.menu.getEmbeddingsModel();
|
||||
vscode.commands.executeCommand('llama-vscode.webview.postMessage', {
|
||||
command: 'updateEmbeddingsModel',
|
||||
model: modelName || 'No model selected'
|
||||
model: currentEmbeddingsModel.name || 'No model selected'
|
||||
});
|
||||
}
|
||||
|
||||
private updateChatModel(status: string = "") {
|
||||
const currentChatModel: LlmModel = this.app.getModel(ModelType.Chat);
|
||||
let modelName = currentChatModel.name
|
||||
if (this.app.configuration.health_check_chat_enabled && status && status.toLowerCase() != "ok")
|
||||
modelName += ": " + status;
|
||||
private updateChatModel() {
|
||||
const currentChatModel: LlmModel = this.app.menu.getChatModel();
|
||||
vscode.commands.executeCommand('llama-vscode.webview.postMessage', {
|
||||
command: 'updateChatModel',
|
||||
model: modelName || 'No model selected'
|
||||
model: currentChatModel.name || 'No model selected'
|
||||
});
|
||||
}
|
||||
|
||||
private updateToolsModel(status: string = "") {
|
||||
const currentToolsModel: LlmModel = this.app.getModel(ModelType.Tools);
|
||||
let modelName = currentToolsModel.name
|
||||
if (this.app.configuration.health_check_tools_enabled && status && status.toLowerCase() != "ok")
|
||||
modelName += ": " + status;
|
||||
private updateToolsModel() {
|
||||
const currentToolsModel: LlmModel = this.app.menu.getToolsModel();
|
||||
vscode.commands.executeCommand('llama-vscode.webview.postMessage', {
|
||||
command: 'updateToolsModel',
|
||||
model: modelName || 'No model selected'
|
||||
model: currentToolsModel.name || 'No model selected'
|
||||
});
|
||||
}
|
||||
|
||||
private updateTmpAgentModel() {
|
||||
const currentTmpAgentModel: LlmModel = this.app.getTmpAgentModel();
|
||||
vscode.commands.executeCommand('llama-vscode.webview.postMessage', {
|
||||
command: 'updateTmpAgentModel',
|
||||
model: currentTmpAgentModel.name || ''
|
||||
});
|
||||
}
|
||||
|
||||
public updateComplsModel(status: string = "") {
|
||||
const currentComplModel: LlmModel = this.app.getModel(ModelType.Completion);
|
||||
let modelName = currentComplModel.name
|
||||
if (this.app.configuration.health_check_compl_enabled && status && status.toLowerCase() != "ok")
|
||||
modelName += ": " + status;
|
||||
private updateComplsModel() {
|
||||
const currentToolsModel: LlmModel = this.app.menu.getComplModel();
|
||||
vscode.commands.executeCommand('llama-vscode.webview.postMessage', {
|
||||
command: 'updateCompletionModel',
|
||||
model: modelName || 'No model selected'
|
||||
model: currentToolsModel.name || 'No model selected'
|
||||
});
|
||||
}
|
||||
|
||||
private updateAgent() {
|
||||
const currentAgent: Agent = this.app.getAgent();
|
||||
const currentAgent: Agent = this.app.menu.getAgent();
|
||||
vscode.commands.executeCommand('llama-vscode.webview.postMessage', {
|
||||
command: 'updateAgent',
|
||||
agent: currentAgent.name || 'No agent selected'
|
||||
|
|
@ -466,7 +278,7 @@ export class LlamaWebviewProvider implements vscode.WebviewViewProvider {
|
|||
}
|
||||
|
||||
private updateEnv() {
|
||||
const currentEnv: Env = this.app.getEnv();
|
||||
const currentEnv: Env = this.app.menu.getEnv();
|
||||
vscode.commands.executeCommand('llama-vscode.webview.postMessage', {
|
||||
command: 'updateEnv',
|
||||
model: currentEnv.name || 'No env selected'
|
||||
|
|
@ -501,45 +313,14 @@ 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 showAgentEditor() {
|
||||
vscode.commands.executeCommand('extension.showLlamaWebview');
|
||||
setTimeout(() => this.setView("agenteditor"), 400);
|
||||
}
|
||||
|
||||
public updateLlamaView() {
|
||||
this.updateModels();
|
||||
this.updateTmpAgentModel();
|
||||
this.updateToolsModel();
|
||||
this.updateChatModel();
|
||||
this.updateEmbsModel();
|
||||
this.updateComplsModel();
|
||||
this.updateAgent();
|
||||
this.updateEnv();
|
||||
this.updateSettingsInView();
|
||||
this.logInUi(this.app.llamaAgent.getAgentLogText())
|
||||
}
|
||||
|
||||
public updateModels() {
|
||||
this.updateToolsModel(this.app.getModelState(ModelType.Tools));
|
||||
this.updateChatModel(this.app.getModelState(ModelType.Chat));
|
||||
this.updateEmbsModel(this.app.getModelState(ModelType.Embeddings));
|
||||
this.updateComplsModel(this.app.getModelState(ModelType.Completion));
|
||||
}
|
||||
|
||||
public updateContextFilesInfo() {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import * as crypto from 'crypto';
|
|||
|
||||
export class LRUCache {
|
||||
private capacity: number;
|
||||
private map: Map<string, string[]>;
|
||||
private map: Map<string, string>;
|
||||
|
||||
constructor(capacity: number) {
|
||||
if (capacity <= 0) {
|
||||
|
|
@ -18,7 +18,7 @@ export class LRUCache {
|
|||
* @param key The key to retrieve.
|
||||
* @returns The value associated with the key, or undefined if the key is not found.
|
||||
*/
|
||||
get = (key: string): string[] | undefined => {
|
||||
get = (key: string): string | undefined => {
|
||||
if (!this.map.has(key)) {
|
||||
return undefined;
|
||||
}
|
||||
|
|
@ -37,7 +37,7 @@ export class LRUCache {
|
|||
* @param key The key to insert or update.
|
||||
* @param value The value to associate with the key.
|
||||
*/
|
||||
put = (key: string, value: string[]): void => {
|
||||
put = (key: string, value: string): void => {
|
||||
if (this.map.has(key)) {
|
||||
// If the key exists, delete it to refresh its position
|
||||
this.map.delete(key);
|
||||
|
|
|
|||
1500
src/menu.ts
1500
src/menu.ts
File diff suppressed because it is too large
Load diff
109
src/prompts.ts
109
src/prompts.ts
|
|
@ -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 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 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 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
|
||||
|
|
@ -202,114 +202,7 @@ def new_function():
|
|||
|
||||
Following these instructions will ensure your edits can be properly applied to the document.
|
||||
`
|
||||
// Reused from Roocode. Thanks for the authors for keeping it open source.
|
||||
TOOL_UPDATE_TODO_LIST_DESCRIPTION = `## update_todo_list
|
||||
|
||||
**Description:**
|
||||
Replace the entire TODO list with an updated checklist reflecting the current state. Always provide the full list; the system will overwrite the previous one. This tool is designed for step-by-step task tracking, allowing you to confirm completion of each step before updating, update multiple task statuses at once (e.g., mark one as completed and start the next), and dynamically add new todos discovered during long or complex tasks.
|
||||
|
||||
**Checklist Format:**
|
||||
- Use a single-level markdown checklist (no nesting or subtasks).
|
||||
- List todos in the intended execution order.
|
||||
- Status options:
|
||||
- [ ] Task description (pending)
|
||||
- [x] Task description (completed)
|
||||
- [-] Task description (in progress)
|
||||
|
||||
**Status Rules:**
|
||||
- [ ] = pending (not started)
|
||||
- [x] = completed (fully finished, no unresolved issues)
|
||||
- [-] = in_progress (currently being worked on)
|
||||
|
||||
**Core Principles:**
|
||||
- Before updating, always confirm which todos have been completed since the last update.
|
||||
- You may update multiple statuses in a single update (e.g., mark the previous as completed and the next as in progress).
|
||||
- When a new actionable item is discovered during a long or complex task, add it to the todo list immediately.
|
||||
- Do not remove any unfinished todos unless explicitly instructed.
|
||||
- Always retain all unfinished tasks, updating their status as needed.
|
||||
- Only mark a task as completed when it is fully accomplished (no partials, no unresolved dependencies).
|
||||
- If a task is blocked, keep it as in_progress and add a new todo describing what needs to be resolved.
|
||||
- Remove tasks only if they are no longer relevant or if the user requests deletion.
|
||||
|
||||
**Usage Example:**
|
||||
<update_todo_list>
|
||||
<todos>
|
||||
[x] Analyze requirements
|
||||
[x] Design architecture
|
||||
[-] Implement core logic
|
||||
[ ] Write tests
|
||||
[ ] Update documentation
|
||||
</todos>
|
||||
</update_todo_list>
|
||||
|
||||
*After completing "Implement core logic" and starting "Write tests":*
|
||||
<update_todo_list>
|
||||
<todos>
|
||||
[x] Analyze requirements
|
||||
[x] Design architecture
|
||||
[x] Implement core logic
|
||||
[-] Write tests
|
||||
[ ] Update documentation
|
||||
[ ] Add performance benchmarks
|
||||
</todos>
|
||||
</update_todo_list>
|
||||
|
||||
**When to Use:**
|
||||
- The task is complicated or involves multiple steps or requires ongoing tracking.
|
||||
- You need to update the status of several todos at once.
|
||||
- New actionable items are discovered during task execution.
|
||||
- The user requests a todo list or provides multiple tasks.
|
||||
- The task is complex and benefits from clear, stepwise progress tracking.
|
||||
|
||||
**When NOT to Use:**
|
||||
- There is only a single, trivial task.
|
||||
- The task can be completed in one or two simple steps.
|
||||
- The request is purely conversational or informational.
|
||||
|
||||
**Task Management Guidelines:**
|
||||
- Mark task as completed immediately after all work of the current task is done.
|
||||
- Start the next task by marking it as in_progress.
|
||||
- Add new todos as soon as they are identified.
|
||||
- Use clear, descriptive task names.
|
||||
`
|
||||
|
||||
TOOL_UPDATE_TODO_LIST_PARAMETER_DESCRIPTION = `Full markdown checklist in execution order, using [ ] for pending, [x] for completed, and [-] for in progress`
|
||||
|
||||
TOOL_DELEGATE_TASK_DESCRIPTION = `Delegates a specific task to a subagent.
|
||||
Use this when you encounter a subtask that is better handled by a dedicated agent (e.g. providing help for llama.vscode, performing calculations, retrieving specific data) or for optimizing context length.
|
||||
Provide the subagent's name and a clear, self-contained description of the task to be performed.
|
||||
Optionally, include relevant context (such as user preferences or key conversation snippets) to help the subagent.
|
||||
The subagent will execute the task using its own tools and return a result.
|
||||
If the delegation fails, an error status with details will be returned.`
|
||||
|
||||
TOOL_CREATE_AGENT_DESCRIPTION = `Creates a new agent in the system. The agent's configuration must be provided as a JSON string conforming to the schema defined in the description of property "agent_json".
|
||||
Upon successful creation, returns a confirmation message containing the unique identifier of the new agent. Ensure that any tool names listed in the tools field correspond to existing tools in the system.`
|
||||
|
||||
PROPERTY_AGENT_JSON_DESCRIPTION = `A JSON string that defines the agent to be created. The object must include the following fields:
|
||||
|
||||
name (string): The name of the agent.
|
||||
|
||||
description (string): A brief explanation of the agent's purpose and behavior.
|
||||
|
||||
subagentEnabled (boolean): Set to true if this agent can be invoked as a subagent by other agents; otherwise false.
|
||||
|
||||
systemInstruction (string): The system prompt or instruction that guides the agent's responses and actions.
|
||||
|
||||
tools (string, optional): A comma-separated list of tool names that the agent is permitted to use. Do not include spaces around the commas (e.g., "tool1,tool2,tool3"). If omitted, the agent will have no tools.
|
||||
|
||||
Example value:
|
||||
{
|
||||
"name": "CustomerSupportAgent",
|
||||
"description": "Handles customer inquiries and returns troubleshooting steps.",
|
||||
"subagentEnabled": true,
|
||||
"systemInstruction": "You are a helpful customer support representative...",
|
||||
"tools": "searchKnowledgeBase,ticketCreator"
|
||||
}
|
||||
`
|
||||
|
||||
SUBAGENTS_DESCRIPTION = `Subagents
|
||||
You have access to specialized subagents via the delegate_task tool. Use it when you encounter a well‑defined subtask that can be handled independently — for example, providing help for llama.vscode, performing complex calculations, or retrieving data from a specific source.
|
||||
If the delegation fails (error or timeout), decide whether to retry with a different subagent, handle the task yourself, or report the issue to the user.`
|
||||
|
||||
constructor(application: Application) {
|
||||
this.app = application;
|
||||
|
|
|
|||
|
|
@ -1,198 +0,0 @@
|
|||
|
||||
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(", ") : "");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,399 +0,0 @@
|
|||
import * as vscode from "vscode";
|
||||
import { QuickPickItem } from "vscode";
|
||||
import { Application } from "../application";
|
||||
import { Agent } from "../types";
|
||||
import { Utils } from "../utils";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import { PREDEFINED_LISTS } from "../lists";
|
||||
import { UI_TEXT_KEYS, PERSISTENCE_KEYS, SETTING_NAME_FOR_LIST, PREDEFINED_LISTS_KEYS, ModelType } from "../constants";
|
||||
|
||||
export class AgentService {
|
||||
private app: Application;
|
||||
public editedAgentTools: Map<string,string> = new Map();
|
||||
|
||||
constructor(app: Application) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
getActions(): vscode.QuickPickItem[] {
|
||||
return [
|
||||
{
|
||||
label: this.app.configuration.getUiText(UI_TEXT_KEYS.selectStartAgent) ?? ""
|
||||
},
|
||||
{
|
||||
label: this.app.configuration.getUiText(UI_TEXT_KEYS.deselectStopAgent) ?? ""
|
||||
},
|
||||
{
|
||||
label: this.app.configuration.getUiText(UI_TEXT_KEYS.addAgent) ?? "",
|
||||
},
|
||||
{
|
||||
label: this.app.configuration.getUiText(UI_TEXT_KEYS.editAgent) ?? "",
|
||||
},
|
||||
{
|
||||
label: this.app.configuration.getUiText(UI_TEXT_KEYS.copyAgent) ?? "",
|
||||
},
|
||||
{
|
||||
label: this.app.configuration.getUiText(UI_TEXT_KEYS.viewAgentDetails) ?? ""
|
||||
},
|
||||
{
|
||||
label: this.app.configuration.getUiText(UI_TEXT_KEYS.deleteAgent) ?? ""
|
||||
},
|
||||
{
|
||||
label: this.app.configuration.getUiText(UI_TEXT_KEYS.exportAgent) ?? ""
|
||||
},
|
||||
{
|
||||
label: this.app.configuration.getUiText(UI_TEXT_KEYS.importAgent) ?? ""
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
async processActions(): Promise<void> {
|
||||
let agentsActions: vscode.QuickPickItem[] = this.app.agentService.getActions();
|
||||
let actionSelected = await vscode.window.showQuickPick(agentsActions);
|
||||
if (!actionSelected) return
|
||||
switch (actionSelected.label) {
|
||||
case this.app.configuration.getUiText(UI_TEXT_KEYS.selectStartAgent):
|
||||
await this.pickAndSelectAgent(this.app.configuration.agents_list);
|
||||
break;
|
||||
case this.app.configuration.getUiText(UI_TEXT_KEYS.addAgent):
|
||||
await this.addAgent(this.app.configuration.agents_list, SETTING_NAME_FOR_LIST.AGENTS);
|
||||
break;
|
||||
case this.app.configuration.getUiText(UI_TEXT_KEYS.editAgent):
|
||||
this.editAgent(this.app.configuration.agents_list);
|
||||
break;
|
||||
case this.app.configuration.getUiText(UI_TEXT_KEYS.copyAgent):
|
||||
this.copyAgent();
|
||||
break;
|
||||
case this.app.configuration.getUiText(UI_TEXT_KEYS.deleteAgent):
|
||||
await this.deleteAgent();
|
||||
break;
|
||||
case this.app.configuration.getUiText(UI_TEXT_KEYS.viewAgentDetails):
|
||||
await this.viewAgent(this.app.configuration.agents_list);
|
||||
break;
|
||||
case this.app.configuration.getUiText(UI_TEXT_KEYS.deselectStopAgent):
|
||||
await this.deselectAgent();
|
||||
break;
|
||||
case this.app.configuration.getUiText(UI_TEXT_KEYS.exportAgent):
|
||||
await this.exportAgent(this.app.configuration.agents_list);
|
||||
break;
|
||||
case this.app.configuration.getUiText(UI_TEXT_KEYS.importAgent):
|
||||
await this.importAgent(this.app.configuration.agents_list, SETTING_NAME_FOR_LIST.AGENTS);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public async pickAndSelectAgent(agentsList: Agent[]): Promise<Agent | undefined> {
|
||||
let allAgents = agentsList.concat(PREDEFINED_LISTS.get(PREDEFINED_LISTS_KEYS.AGENTS) as Agent[]);
|
||||
let agentsItems: QuickPickItem[] = this.getStandardQpList(agentsList, "");
|
||||
agentsItems = agentsItems.concat(this.getStandardQpList(PREDEFINED_LISTS.get(PREDEFINED_LISTS_KEYS.AGENTS) as Agent[], "(predefined) ", agentsList.length));
|
||||
let lastUsedAgent = this.app.persistence.getValue(PERSISTENCE_KEYS.SELECTED_AGENT) as Agent;
|
||||
if (lastUsedAgent && lastUsedAgent.name.trim() !== "") {
|
||||
agentsItems.push({ label: (agentsItems.length + 1) + ". Last used agent", description: lastUsedAgent.name });
|
||||
}
|
||||
const agentItem = await vscode.window.showQuickPick(agentsItems);
|
||||
if (agentItem) {
|
||||
let selectedAgent: Agent;
|
||||
if (agentItem.label.includes("Last used agent")) {
|
||||
selectedAgent = lastUsedAgent;
|
||||
} else {
|
||||
const index = parseInt(agentItem.label.split(". ")[0], 10) - 1;
|
||||
selectedAgent = allAgents[index];
|
||||
}
|
||||
if (selectedAgent) {
|
||||
await this.selectAgent(selectedAgent);
|
||||
vscode.window.showInformationMessage(`Agent is selected: ${selectedAgent.name}`);
|
||||
return selectedAgent;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async selectAgent(agent: Agent): Promise<void> {
|
||||
this.app.setAgent(agent);
|
||||
const allTools = Array.from(this.app.tools.toolsFunc.keys());
|
||||
for (let toolName of allTools) {
|
||||
try {
|
||||
await this.app.configuration.updateConfigValue(`tool_${toolName}_enabled`, agent.tools?.includes(toolName) ?? false);
|
||||
} catch (err) {
|
||||
vscode.window.showErrorMessage("Error updating tools configuration.")
|
||||
}
|
||||
}
|
||||
if (agent.toolsModel && agent.toolsModel.name) {
|
||||
await this.app.modelService.selectStartModel(agent.toolsModel, ModelType.Tools, this.app.modelService.getTypeDetails(ModelType.Tools))
|
||||
}
|
||||
await this.app.persistence.setValue(PERSISTENCE_KEYS.SELECTED_AGENT, agent);
|
||||
this.app.llamaWebviewProvider.updateLlamaView();
|
||||
if (agent.name.trim() !== "") {
|
||||
vscode.window.showInformationMessage(`Agent ${agent.name} is selected.`);
|
||||
}
|
||||
}
|
||||
|
||||
async deselectAgent(): Promise<void> {
|
||||
const emptyAgent = { name: "", systemInstruction: [] };
|
||||
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);
|
||||
}
|
||||
await this.app.persistence.setValue(PERSISTENCE_KEYS.SELECTED_AGENT, emptyAgent);
|
||||
this.app.llamaWebviewProvider.updateLlamaView();
|
||||
vscode.window.showInformationMessage("The agent is deselected.");
|
||||
}
|
||||
|
||||
async addAgent(agentsList: Agent[], settingName: string): Promise<void> {
|
||||
this.app.llamaWebviewProvider.showAgentEditor();
|
||||
this.app.llamaWebviewProvider.addEditAgent({name: "", description: "", systemInstruction: [], tools: []})
|
||||
}
|
||||
|
||||
selectTools = async (currentTools: string[]): Promise<string[]> => {
|
||||
const availableTools = Array.from(this.app.tools.toolsFunc.keys()).map(tool => ({
|
||||
label: tool,
|
||||
picked: currentTools.includes(tool)
|
||||
}));
|
||||
const selectedToolsItems = await vscode.window.showQuickPick(availableTools, {
|
||||
canPickMany: true,
|
||||
placeHolder: 'Select tools for the agent'
|
||||
});
|
||||
const tools = selectedToolsItems ? selectedToolsItems.map(item => item.label) : Array.from(this.app.tools.toolsFunc.keys());
|
||||
|
||||
return tools;
|
||||
}
|
||||
|
||||
private async persistNewAgent(newAgent: Agent, agentsList: Agent[], settingName: string, confirmMessage: string): Promise<void> {
|
||||
let agentDetails = this.getAgentDetailsAsString(newAgent);
|
||||
const shouldAddAgent = await Utils.confirmAction(confirmMessage, agentDetails);
|
||||
|
||||
if (shouldAddAgent) {
|
||||
agentsList.push(newAgent);
|
||||
this.app.configuration.updateConfigValue(settingName, agentsList);
|
||||
vscode.window.showInformationMessage("The agent is added.");
|
||||
}
|
||||
}
|
||||
|
||||
private async persistEditedAgent(editedAgent: Agent, agentsList: Agent[], settingName: string): Promise<void> {
|
||||
let agentDetails = this.getAgentDetailsAsString(editedAgent);
|
||||
const shouldAddAgent = await Utils.confirmAction("Do you want to update agent?", agentDetails);
|
||||
|
||||
if (shouldAddAgent) {
|
||||
let agentExisting = agentsList.find(agn => agn.name.trim() == editedAgent.name.trim())
|
||||
if (agentExisting){
|
||||
agentExisting.description = editedAgent.description
|
||||
agentExisting.subagentEnabled = editedAgent.subagentEnabled
|
||||
agentExisting.systemInstruction = editedAgent.systemInstruction
|
||||
agentExisting.toolsModel = editedAgent.toolsModel
|
||||
agentExisting.tools = editedAgent.tools
|
||||
this.app.configuration.updateConfigValue(settingName, agentsList);
|
||||
vscode.window.showInformationMessage("The agent is updated: " + agentExisting.name);
|
||||
} else {
|
||||
vscode.window.showWarningMessage("The agent to update is not found!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addUpdateAgent = async (agentToAddUpdate: Agent) => {
|
||||
let agentsList = this.app.configuration.agents_list;
|
||||
if (agentsList.findIndex(agn => agn.name.trim() == agentToAddUpdate.name.trim()) == -1){
|
||||
await this.persistNewAgent(
|
||||
agentToAddUpdate,
|
||||
agentsList,
|
||||
SETTING_NAME_FOR_LIST.AGENTS,
|
||||
"A new agent will be added. Do you want to add the agent?"
|
||||
);
|
||||
} else {
|
||||
await this.persistEditedAgent(agentToAddUpdate, agentsList, SETTING_NAME_FOR_LIST.AGENTS)
|
||||
}
|
||||
}
|
||||
|
||||
async deleteAgent(): Promise<void> {
|
||||
let agentsList: Agent[] = this.app.configuration.agents_list;
|
||||
const agentsItems: QuickPickItem[] = this.getStandardQpList(agentsList, "");
|
||||
const agentItem = await vscode.window.showQuickPick(agentsItems);
|
||||
if (agentItem) {
|
||||
let agentIndex = parseInt(agentItem.label.split(". ")[0], 10) - 1;
|
||||
const shouldDeleteAgent = await Utils.confirmAction("Are you sure you want to delete the following agent?",
|
||||
this.getAgentDetailsAsString(agentsList[agentIndex])
|
||||
);
|
||||
if (shouldDeleteAgent) {
|
||||
agentsList.splice(agentIndex, 1);
|
||||
this.app.configuration.updateConfigValue(SETTING_NAME_FOR_LIST.AGENTS, agentsList);
|
||||
vscode.window.showInformationMessage("The agent is deleted.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async editAgent(agentsList: Agent[]): Promise<void> {
|
||||
const agentsItems: QuickPickItem[] = this.getStandardQpList(agentsList, "");
|
||||
const agentItem = await vscode.window.showQuickPick(agentsItems);
|
||||
if (agentItem) {
|
||||
let agentIndex = parseInt(agentItem.label.split(". ")[0], 10) - 1;
|
||||
this.app.llamaWebviewProvider.showAgentEditor();
|
||||
setTimeout(() => {
|
||||
this.app.llamaWebviewProvider.addEditAgent(agentsList[agentIndex])
|
||||
}, 500);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
async copyAgent(): Promise<void> {
|
||||
let agentsList = this.app.configuration.agents_list
|
||||
let allAgents = agentsList.concat(PREDEFINED_LISTS.get(PREDEFINED_LISTS_KEYS.AGENTS) as Agent[]);
|
||||
let agentsItems: QuickPickItem[] = this.getStandardQpList(agentsList, "");
|
||||
agentsItems = agentsItems.concat(this.getStandardQpList(PREDEFINED_LISTS.get(PREDEFINED_LISTS_KEYS.AGENTS) as Agent[], "(predefined) ", agentsList.length));
|
||||
let agentItem = await vscode.window.showQuickPick(agentsItems);
|
||||
if (agentItem) {
|
||||
let agentIndex = parseInt(agentItem.label.split(". ")[0], 10) - 1;
|
||||
this.app.llamaWebviewProvider.showAgentEditor();
|
||||
const selectedAgent = allAgents[agentIndex];
|
||||
const newAgent: Agent = {
|
||||
name: "Copy of " + agentItem.label,
|
||||
description: selectedAgent.description,
|
||||
systemInstruction: selectedAgent.systemInstruction,
|
||||
tools: selectedAgent.tools
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.app.llamaWebviewProvider.addEditAgent(newAgent)
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// let agentsItems: QuickPickItem[] = this.getStandardQpList(agentsList, "");
|
||||
// agentsItems = agentsItems.concat(this.getStandardQpList(PREDEFINED_LISTS.get(PREDEFINED_LISTS_KEYS.AGENTS) as Agent[], "(predefined) ", agentsList.length));
|
||||
// const agentItem = await vscode.window.showQuickPick(agentsItems);
|
||||
// if (agentItem) {
|
||||
// let agentIndex = parseInt(agentItem.label.split(". ")[0], 10) - 1;
|
||||
// this.app.llamaWebviewProvider.showAgentEditor();
|
||||
// const selectedAgent = agentsList[agentIndex];
|
||||
// const newAgent: Agent = {
|
||||
// name: "Copyt of " + selectedAgent.name,
|
||||
// description: selectedAgent.description,
|
||||
// systemInstruction: selectedAgent.systemInstruction,
|
||||
// tools: selectedAgent.tools
|
||||
// }
|
||||
// setTimeout(() => {
|
||||
// this.app.llamaWebviewProvider.addEditAgent(newAgent)
|
||||
// }, 500);
|
||||
|
||||
// }
|
||||
}
|
||||
|
||||
async viewAgent(agentsList: Agent[]): Promise<void> {
|
||||
let allAgents = agentsList.concat(PREDEFINED_LISTS.get(PREDEFINED_LISTS_KEYS.AGENTS) as Agent[]);
|
||||
let agentsItems: QuickPickItem[] = this.getStandardQpList(agentsList, "");
|
||||
agentsItems = agentsItems.concat(this.getStandardQpList(PREDEFINED_LISTS.get(PREDEFINED_LISTS_KEYS.AGENTS) as Agent[], "(predefined) ", agentsList.length));
|
||||
let agentItem = await vscode.window.showQuickPick(agentsItems);
|
||||
if (agentItem) {
|
||||
let agentIndex = parseInt(agentItem.label.split(". ")[0], 10) - 1;
|
||||
let selectedAgent = allAgents[agentIndex];
|
||||
await this.showAgentDetails(selectedAgent);
|
||||
}
|
||||
}
|
||||
|
||||
public async showAgentDetails(selectedAgent: Agent) {
|
||||
let agentDetails = this.getAgentDetailsAsString(selectedAgent);
|
||||
await Utils.showOkDialog(agentDetails);
|
||||
}
|
||||
|
||||
async exportAgent(agentsList: Agent[]): Promise<void> {
|
||||
let allAgents = agentsList.concat(PREDEFINED_LISTS.get(PREDEFINED_LISTS_KEYS.AGENTS) as Agent[]);
|
||||
let agentsItems: QuickPickItem[] = this.getStandardQpList(agentsList, "");
|
||||
agentsItems = agentsItems.concat(this.getStandardQpList(PREDEFINED_LISTS.get(PREDEFINED_LISTS_KEYS.AGENTS) as Agent[], "(predefined) ", agentsList.length));
|
||||
let agentItem = await vscode.window.showQuickPick(agentsItems);
|
||||
if (agentItem) {
|
||||
let agentIndex = parseInt(agentItem.label.split(". ")[0], 10) - 1;
|
||||
let selectedAgent = allAgents[agentIndex];
|
||||
let shouldExport = await Utils.showYesNoDialog("Do you want to export the following agent? \n\n" +
|
||||
this.getAgentDetailsAsString(selectedAgent)
|
||||
);
|
||||
|
||||
if (shouldExport) {
|
||||
const uri = await vscode.window.showSaveDialog({
|
||||
defaultUri: vscode.Uri.file(path.join(vscode.workspace.rootPath || '', selectedAgent.name + '.json')),
|
||||
filters: {
|
||||
'Agent Files': ['json'],
|
||||
'All Files': ['*']
|
||||
},
|
||||
saveLabel: 'Export Agent'
|
||||
});
|
||||
|
||||
if (uri) {
|
||||
const jsonContent = JSON.stringify(selectedAgent, null, 2);
|
||||
fs.writeFileSync(uri.fsPath, jsonContent, 'utf8');
|
||||
vscode.window.showInformationMessage("Agent is saved.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async importAgent(agentsList: Agent[], settingName: string): Promise<void> {
|
||||
const uris = await vscode.window.showOpenDialog({
|
||||
canSelectMany: false,
|
||||
openLabel: 'Import Agent',
|
||||
filters: {
|
||||
'Agent Files': ['json'],
|
||||
'All Files': ['*']
|
||||
},
|
||||
});
|
||||
|
||||
if (!uris || uris.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filePath = uris[0].fsPath;
|
||||
const fileContent = fs.readFileSync(filePath, 'utf8');
|
||||
let newAgent: Agent = JSON.parse(fileContent);
|
||||
|
||||
// Sanitize imported agent
|
||||
this.sanitizeAgent(newAgent);
|
||||
|
||||
await this.persistNewAgent(newAgent, agentsList, settingName,"A new agent will be added. Do you want to add the agent?");
|
||||
}
|
||||
|
||||
private sanitizeAgent(agent: Agent): void {
|
||||
if (agent.name) agent.name = this.app.modelService.sanitizeInput(agent.name);
|
||||
if (agent.description) agent.description = this.app.modelService.sanitizeInput(agent.description);
|
||||
if (agent.systemInstruction) {
|
||||
agent.systemInstruction = agent.systemInstruction.map((s: string) => this.app.modelService.sanitizeInput(s));
|
||||
}
|
||||
// tools are strings, no need
|
||||
}
|
||||
|
||||
public getAgentDetailsAsString(agent: Agent): string {
|
||||
return "Agent details: " +
|
||||
"\nname: " + agent.name +
|
||||
"\ndescription: " + agent.description +
|
||||
"\nsystem prompt: \n" + agent.systemInstruction.join("\n") +
|
||||
"\nsubagent enabled: " + agent.subagentEnabled +
|
||||
"\ntools model: \n" + agent.toolsModel?.name +
|
||||
"\n\ntools: " + (agent.tools ? agent.tools.join(", ") : "");
|
||||
}
|
||||
|
||||
private getStandardQpList(list: Agent[], prefix: string, lastAgentNumber: number = 0): QuickPickItem[] {
|
||||
const items: QuickPickItem[] = [];
|
||||
let i = lastAgentNumber;
|
||||
for (let elem of list) {
|
||||
i++;
|
||||
items.push({
|
||||
label: i + ". " + prefix + elem.name,
|
||||
description: elem.description,
|
||||
});
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
resetEditedAgentTools = () => {
|
||||
this.editedAgentTools.clear();
|
||||
this.app.llamaWebviewProvider.updateContextFilesInfo();
|
||||
}
|
||||
|
||||
addEditedAgentTools = (toolName: string, toolDescription: string) => {
|
||||
this.editedAgentTools.set(toolName, toolDescription);
|
||||
}
|
||||
|
||||
removeEditedAgentTools = (toolName: string) => {
|
||||
this.editedAgentTools.delete(toolName);
|
||||
}
|
||||
|
||||
getEditedAgentTools = () => {
|
||||
return this.editedAgentTools;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,183 +0,0 @@
|
|||
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.setChat(chatToSelect);
|
||||
await this.app.persistence.setValue(PERSISTENCE_KEYS.SELECTED_CHAT, this.app.getChat());
|
||||
} else {
|
||||
await this.updateChatHistory();
|
||||
this.app.setChat(chatToSelect);
|
||||
await this.app.persistence.setValue(PERSISTENCE_KEYS.SELECTED_CHAT, chatToSelect);
|
||||
await this.app.llamaAgent.selectChat(chatToSelect);
|
||||
this.app.llamaWebviewProvider.updateLlamaView();
|
||||
}
|
||||
}
|
||||
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,490 +0,0 @@
|
|||
|
||||
import * as vscode from "vscode";
|
||||
import { QuickPickItem } from "vscode";
|
||||
import { Application } from "../application";
|
||||
import { Env } from "../types";
|
||||
import { Utils } from "../utils";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import { PREDEFINED_LISTS } from "../lists";
|
||||
import { ModelType, UI_TEXT_KEYS, PERSISTENCE_KEYS, SETTING_NAME_FOR_LIST, PREDEFINED_LISTS_KEYS } from "../constants";
|
||||
|
||||
export class EnvService {
|
||||
private app: Application;
|
||||
|
||||
constructor(app: Application) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
getActions(): vscode.QuickPickItem[] {
|
||||
return [
|
||||
{
|
||||
label: this.app.configuration.getUiText(UI_TEXT_KEYS.selectStartEnv) ?? ""
|
||||
},
|
||||
{
|
||||
label: this.app.configuration.getUiText(UI_TEXT_KEYS.deselectStopEnv) ?? ""
|
||||
},
|
||||
{
|
||||
label: this.app.configuration.getUiText(UI_TEXT_KEYS.addEnv) ?? "",
|
||||
description: this.app.configuration.getUiText(UI_TEXT_KEYS.addEnvDescription) ?? "",
|
||||
},
|
||||
{
|
||||
label: this.app.configuration.getUiText(UI_TEXT_KEYS.viewEnvDetails) ?? ""
|
||||
},
|
||||
{
|
||||
label: this.app.configuration.getUiText(UI_TEXT_KEYS.deleteEnv) ?? ""
|
||||
},
|
||||
{
|
||||
label: this.app.configuration.getUiText(UI_TEXT_KEYS.exportEnv) ?? ""
|
||||
},
|
||||
{
|
||||
label: this.app.configuration.getUiText(UI_TEXT_KEYS.importEnv) ?? ""
|
||||
},
|
||||
{
|
||||
label: this.app.configuration.getUiText(UI_TEXT_KEYS.downloadUploadEnvsOnline) ?? ""
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
async processActions(selected: vscode.QuickPickItem): Promise<void> {
|
||||
switch (selected.label) {
|
||||
case this.app.configuration.getUiText(UI_TEXT_KEYS.selectStartEnv):
|
||||
await this.selectEnv(this.app.configuration.envs_list, true);
|
||||
break;
|
||||
case this.app.configuration.getUiText(UI_TEXT_KEYS.addEnv):
|
||||
this.app.llamaWebviewProvider.showEnvView();
|
||||
break;
|
||||
break;
|
||||
case this.app.configuration.getUiText(UI_TEXT_KEYS.deleteEnv):
|
||||
await this.deleteEnv(this.app.configuration.envs_list, SETTING_NAME_FOR_LIST.ENVS);
|
||||
break;
|
||||
case this.app.configuration.getUiText(UI_TEXT_KEYS.viewEnvDetails):
|
||||
await this.viewEnv(this.app.configuration.envs_list);
|
||||
break;
|
||||
case this.app.configuration.getUiText(UI_TEXT_KEYS.deselectStopEnv):
|
||||
await this.stopEnv();
|
||||
break;
|
||||
case this.app.configuration.getUiText(UI_TEXT_KEYS.exportEnv):
|
||||
await this.exportEnv(this.app.configuration.envs_list);
|
||||
break;
|
||||
case this.app.configuration.getUiText(UI_TEXT_KEYS.importEnv):
|
||||
await this.importEnv(this.app.configuration.envs_list, SETTING_NAME_FOR_LIST.ENVS);
|
||||
break;
|
||||
case this.app.configuration.getUiText(UI_TEXT_KEYS.downloadUploadEnvsOnline):
|
||||
await vscode.env.openExternal(vscode.Uri.parse('https://github.com/ggml-org/llama.vscode/discussions'));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public async selectEnv(envsList: Env[], confirm: boolean): Promise<Env | undefined> {
|
||||
let allEnvs = envsList.concat(PREDEFINED_LISTS.get(PREDEFINED_LISTS_KEYS.ENVS) as Env[]);
|
||||
let envsItems: QuickPickItem[] = this.getStandardQpList(envsList, "");
|
||||
envsItems = envsItems.concat(this.getStandardQpList(PREDEFINED_LISTS.get(PREDEFINED_LISTS_KEYS.ENVS) as Env[], "(predefined) ", envsList.length));
|
||||
let lastUsedEnv = this.app.persistence.getValue(PERSISTENCE_KEYS.SELECTED_ENV) as Env;
|
||||
if (lastUsedEnv && lastUsedEnv.name.trim() !== "") {
|
||||
envsItems.push({ label: (envsItems.length + 1) + ". Last used env", description: lastUsedEnv.name });
|
||||
}
|
||||
const envItem = await vscode.window.showQuickPick(envsItems);
|
||||
if (envItem) {
|
||||
let selectedEnv: Env;
|
||||
if (envItem.label.includes("Last used env")) {
|
||||
selectedEnv = lastUsedEnv;
|
||||
} else {
|
||||
const index = parseInt(envItem.label.split(". ")[0], 10) - 1;
|
||||
selectedEnv = allEnvs[index];
|
||||
}
|
||||
if (selectedEnv) {
|
||||
await this.selectStartEnv(selectedEnv, confirm);
|
||||
return selectedEnv;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async selectStartEnv(env: Env, confirm: boolean = false): Promise<void> {
|
||||
// Get current state for inheritance
|
||||
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;
|
||||
|
||||
// Kill all servers
|
||||
await this.app.llamaServer.killFimCmd();
|
||||
await this.app.llamaServer.killChatCmd();
|
||||
await this.app.llamaServer.killEmbeddingsCmd();
|
||||
await this.app.llamaServer.killToolsCmd();
|
||||
|
||||
let shouldSelect = true;
|
||||
if (confirm) {
|
||||
// Build temp env with inheritance for details
|
||||
const tempEnv: Env = {
|
||||
...env,
|
||||
completion: env.completion ?? currentComplModel,
|
||||
chat: env.chat ?? currentChatModel,
|
||||
embeddings: env.embeddings ?? currentEmbeddingsModel,
|
||||
tools: env.tools ?? currentToolsModel,
|
||||
agent: env.agent ?? currentAgent,
|
||||
ragEnabled: env.ragEnabled ?? currentRagEnabled,
|
||||
envStartLastUsed: env.envStartLastUsed ?? currentEnvStartLastUsed,
|
||||
complEnabled: env.complEnabled ?? currentComplEnabled,
|
||||
};
|
||||
shouldSelect = await Utils.confirmAction(
|
||||
"You are about to select the env below. If there are local models inside, they will be downloaded (if not yet done) and llama.cpp server(s) will be started.\n\n Do you want to continue?",
|
||||
this.getEnvDetailsAsString(tempEnv)
|
||||
);
|
||||
}
|
||||
|
||||
if (shouldSelect && env) {
|
||||
// Set completion model (inherit if not specified)
|
||||
const complModel = env.completion ?? currentComplModel;
|
||||
this.app.setSelectedModel(ModelType.Completion, complModel);
|
||||
if (complModel && complModel.name.trim() !== "") {
|
||||
await this.app.modelService.addApiKey(complModel);
|
||||
if (complModel.localStartCommand) {
|
||||
await this.app.llamaServer.shellFimCmd(this.app.modelService.sanitizeCommand(complModel.localStartCommand));
|
||||
}
|
||||
}
|
||||
|
||||
// Set chat model
|
||||
const chatModel = env.chat ?? currentChatModel;
|
||||
this.app.setSelectedModel(ModelType.Chat, chatModel);
|
||||
if (chatModel && chatModel.name.trim() !== "") {
|
||||
await this.app.modelService.addApiKey(chatModel);
|
||||
if (chatModel.localStartCommand) {
|
||||
await this.app.llamaServer.shellChatCmd(this.app.modelService.sanitizeCommand(chatModel.localStartCommand));
|
||||
}
|
||||
}
|
||||
|
||||
// Set embeddings model
|
||||
const embedModel = env.embeddings ?? currentEmbeddingsModel;
|
||||
this.app.setSelectedModel(ModelType.Embeddings, embedModel);
|
||||
if (embedModel && embedModel.name.trim() !== "") {
|
||||
await this.app.modelService.addApiKey(embedModel);
|
||||
if (embedModel.localStartCommand) {
|
||||
await this.app.llamaServer.shellEmbeddingsCmd(this.app.modelService.sanitizeCommand(embedModel.localStartCommand));
|
||||
}
|
||||
}
|
||||
|
||||
// Set tools model
|
||||
const toolsModel = env.tools ?? currentToolsModel;
|
||||
this.app.setSelectedModel(ModelType.Tools, toolsModel);
|
||||
if (toolsModel && toolsModel.name.trim() !== "") {
|
||||
await this.app.modelService.addApiKey(toolsModel);
|
||||
if (toolsModel.localStartCommand) {
|
||||
await this.app.llamaServer.shellToolsCmd(this.app.modelService.sanitizeCommand(toolsModel.localStartCommand));
|
||||
}
|
||||
}
|
||||
|
||||
// Set agent
|
||||
const agent = env.agent ?? currentAgent;
|
||||
if (agent) {
|
||||
await this.app.agentService.selectAgent(agent);
|
||||
}
|
||||
|
||||
// Set configs if specified in env
|
||||
if (env.ragEnabled !== undefined) {
|
||||
this.app.configuration.updateConfigValue("rag_enabled", env.ragEnabled);
|
||||
}
|
||||
if (env.envStartLastUsed !== undefined) {
|
||||
this.app.configuration.updateConfigValue("env_start_last_used", env.envStartLastUsed);
|
||||
}
|
||||
if (env.complEnabled !== undefined) {
|
||||
this.app.configuration.updateConfigValue("enabled", env.complEnabled);
|
||||
}
|
||||
|
||||
// Set selected env
|
||||
this.app.setSelectedEnv(env);
|
||||
|
||||
this.app.llamaWebviewProvider.updateLlamaView();
|
||||
}
|
||||
}
|
||||
|
||||
async addEnv(envsList: Env[], settingName: string): Promise<void> {
|
||||
let name = await Utils.getValidatedInput(
|
||||
'name for your env (required)',
|
||||
(input) => input.trim() !== '',
|
||||
5,
|
||||
{
|
||||
placeHolder: 'Enter a user friendly name for your env (required)',
|
||||
value: ''
|
||||
}
|
||||
);
|
||||
if (name === undefined) {
|
||||
vscode.window.showInformationMessage("Env addition cancelled.");
|
||||
return;
|
||||
}
|
||||
name = this.app.modelService.sanitizeInput(name);
|
||||
|
||||
let description = await vscode.window.showInputBox({
|
||||
placeHolder: 'description for the env - what is the purpose, when to select etc. ',
|
||||
prompt: 'Enter description for the env.',
|
||||
value: ''
|
||||
});
|
||||
description = this.app.modelService.sanitizeInput(description || '');
|
||||
|
||||
// Inherit from current state
|
||||
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,
|
||||
description: description,
|
||||
completion: currentComplModel.name.trim() !== "" ? currentComplModel : undefined,
|
||||
chat: currentChatModel.name.trim() !== "" ? currentChatModel : undefined,
|
||||
embeddings: currentEmbeddingsModel.name.trim() !== "" ? currentEmbeddingsModel : undefined,
|
||||
tools: currentToolsModel.name.trim() !== "" ? currentToolsModel : undefined,
|
||||
agent: currentAgent.name.trim() !== "" ? currentAgent : undefined,
|
||||
ragEnabled: this.app.configuration.rag_enabled,
|
||||
envStartLastUsed: this.app.configuration.env_start_last_used,
|
||||
complEnabled: this.app.configuration.enabled
|
||||
};
|
||||
|
||||
await this.persistEnv(newEnv, envsList, settingName);
|
||||
}
|
||||
|
||||
private async persistEnv(newEnv: Env, envsList: Env[], settingName: string): Promise<void> {
|
||||
let envDetails = this.getEnvDetailsAsString(newEnv);
|
||||
const shouldAddEnv = await Utils.confirmAction("A new env will be added. Do you want to add the env?", envDetails);
|
||||
|
||||
if (shouldAddEnv) {
|
||||
envsList.push(newEnv);
|
||||
this.app.configuration.updateConfigValue(settingName, envsList);
|
||||
vscode.window.showInformationMessage("The env is added.");
|
||||
}
|
||||
}
|
||||
|
||||
async deleteEnv(envsList: Env[], settingName: string): Promise<void> {
|
||||
const envsItems: QuickPickItem[] = this.getStandardQpList(envsList, "");
|
||||
const envItem = await vscode.window.showQuickPick(envsItems);
|
||||
if (envItem) {
|
||||
let envIndex = parseInt(envItem.label.split(". ")[0], 10) - 1;
|
||||
const shouldDeleteEnv = await Utils.confirmAction("Are you sure you want to delete the following env?",
|
||||
this.getEnvDetailsAsString(envsList[envIndex])
|
||||
);
|
||||
if (shouldDeleteEnv) {
|
||||
envsList.splice(envIndex, 1);
|
||||
this.app.configuration.updateConfigValue(settingName, envsList);
|
||||
vscode.window.showInformationMessage("The env is deleted.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async viewEnv(envsList: Env[]): Promise<void> {
|
||||
let allEnvs = envsList.concat(PREDEFINED_LISTS.get(PREDEFINED_LISTS_KEYS.ENVS) as Env[]);
|
||||
let envsItems: QuickPickItem[] = this.getStandardQpList(envsList, "");
|
||||
envsItems = envsItems.concat(this.getStandardQpList(PREDEFINED_LISTS.get(PREDEFINED_LISTS_KEYS.ENVS) as Env[], "(predefined) ", envsList.length));
|
||||
let envItem = await vscode.window.showQuickPick(envsItems);
|
||||
if (envItem) {
|
||||
let envIndex = parseInt(envItem.label.split(". ")[0], 10) - 1;
|
||||
let selectedEnv = allEnvs[envIndex];
|
||||
let envDetails = this.getEnvDetailsAsString(selectedEnv);
|
||||
await Utils.showOkDialog(envDetails);
|
||||
}
|
||||
}
|
||||
|
||||
async stopEnv(): Promise<void> {
|
||||
await this.app.llamaServer.killFimCmd();
|
||||
this.app.setSelectedModel(ModelType.Completion, { name: "", localStartCommand: "" });
|
||||
await this.app.llamaServer.killChatCmd();
|
||||
this.app.setSelectedModel(ModelType.Chat, { name: "", localStartCommand: "" });
|
||||
await this.app.llamaServer.killEmbeddingsCmd();
|
||||
this.app.setSelectedModel(ModelType.Embeddings, { name: "", localStartCommand: "" });
|
||||
await this.app.llamaServer.killToolsCmd();
|
||||
this.app.setSelectedModel(ModelType.Tools, { name: "", localStartCommand: "" });
|
||||
await this.app.agentService.deselectAgent();
|
||||
this.app.setSelectedEnv({ name: "" });
|
||||
this.app.llamaWebviewProvider.updateLlamaView();
|
||||
vscode.window.showInformationMessage("Env, models and agent are deselected.")
|
||||
}
|
||||
|
||||
async exportEnv(envsList: Env[]): Promise<void> {
|
||||
let allEnvs = envsList.concat(PREDEFINED_LISTS.get(PREDEFINED_LISTS_KEYS.ENVS) as Env[]);
|
||||
let envsItems: QuickPickItem[] = this.getStandardQpList(envsList, "");
|
||||
envsItems = envsItems.concat(this.getStandardQpList(PREDEFINED_LISTS.get(PREDEFINED_LISTS_KEYS.ENVS) as Env[], "(predefined) ", envsList.length));
|
||||
let envItem = await vscode.window.showQuickPick(envsItems);
|
||||
if (envItem) {
|
||||
let envIndex = parseInt(envItem.label.split(". ")[0], 10) - 1;
|
||||
let selectedEnv = allEnvs[envIndex];
|
||||
let shouldExport = await Utils.showYesNoDialog("Do you want to export the following env? \n\n" +
|
||||
this.getEnvDetailsAsString(selectedEnv)
|
||||
);
|
||||
|
||||
if (shouldExport) {
|
||||
const uri = await vscode.window.showSaveDialog({
|
||||
defaultUri: vscode.Uri.file(path.join(vscode.workspace.rootPath || '', selectedEnv.name + '.json')),
|
||||
filters: {
|
||||
'Env Files': ['json'],
|
||||
'All Files': ['*']
|
||||
},
|
||||
saveLabel: 'Export Env'
|
||||
});
|
||||
|
||||
if (uri) {
|
||||
const jsonContent = JSON.stringify(selectedEnv, null, 2);
|
||||
fs.writeFileSync(uri.fsPath, jsonContent, 'utf8');
|
||||
vscode.window.showInformationMessage("Env is saved.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async importEnv(envsList: Env[], settingName: string): Promise<void> {
|
||||
const uris = await vscode.window.showOpenDialog({
|
||||
canSelectMany: false,
|
||||
openLabel: 'Import Env',
|
||||
filters: {
|
||||
'Env Files': ['json'],
|
||||
'All Files': ['*']
|
||||
},
|
||||
});
|
||||
|
||||
if (!uris || uris.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filePath = uris[0].fsPath;
|
||||
const fileContent = fs.readFileSync(filePath, 'utf8');
|
||||
let newEnv: Env = JSON.parse(fileContent);
|
||||
|
||||
// Sanitize imported env
|
||||
this.sanitizeEnv(newEnv);
|
||||
|
||||
await this.persistEnv(newEnv, envsList, settingName);
|
||||
}
|
||||
|
||||
private sanitizeEnv(env: Env): void {
|
||||
if (env.name) env.name = this.app.modelService.sanitizeInput(env.name);
|
||||
if (env.description) env.description = this.app.modelService.sanitizeInput(env.description);
|
||||
|
||||
// Sanitize completion model
|
||||
if (env.completion) {
|
||||
if (env.completion.name) env.completion.name = this.app.modelService.sanitizeInput(env.completion.name);
|
||||
if (env.completion.localStartCommand) env.completion.localStartCommand = this.app.modelService.sanitizeCommand(env.completion.localStartCommand);
|
||||
if (env.completion.endpoint) env.completion.endpoint = this.app.modelService.sanitizeInput(env.completion.endpoint);
|
||||
if (env.completion.aiModel) env.completion.aiModel = this.app.modelService.sanitizeInput(env.completion.aiModel);
|
||||
}
|
||||
|
||||
// Similarly for chat
|
||||
if (env.chat) {
|
||||
if (env.chat.name) env.chat.name = this.app.modelService.sanitizeInput(env.chat.name);
|
||||
if (env.chat.localStartCommand) env.chat.localStartCommand = this.app.modelService.sanitizeCommand(env.chat.localStartCommand);
|
||||
if (env.chat.endpoint) env.chat.endpoint = this.app.modelService.sanitizeInput(env.chat.endpoint);
|
||||
if (env.chat.aiModel) env.chat.aiModel = this.app.modelService.sanitizeInput(env.chat.aiModel);
|
||||
}
|
||||
|
||||
// Embeddings
|
||||
if (env.embeddings) {
|
||||
if (env.embeddings.name) env.embeddings.name = this.app.modelService.sanitizeInput(env.embeddings.name);
|
||||
if (env.embeddings.localStartCommand) env.embeddings.localStartCommand = this.app.modelService.sanitizeCommand(env.embeddings.localStartCommand);
|
||||
if (env.embeddings.endpoint) env.embeddings.endpoint = this.app.modelService.sanitizeInput(env.embeddings.endpoint);
|
||||
if (env.embeddings.aiModel) env.embeddings.aiModel = this.app.modelService.sanitizeInput(env.embeddings.aiModel);
|
||||
}
|
||||
|
||||
// Tools
|
||||
if (env.tools) {
|
||||
if (env.tools.name) env.tools.name = this.app.modelService.sanitizeInput(env.tools.name);
|
||||
if (env.tools.localStartCommand) env.tools.localStartCommand = this.app.modelService.sanitizeCommand(env.tools.localStartCommand);
|
||||
if (env.tools.endpoint) env.tools.endpoint = this.app.modelService.sanitizeInput(env.tools.endpoint);
|
||||
if (env.tools.aiModel) env.tools.aiModel = this.app.modelService.sanitizeInput(env.tools.aiModel);
|
||||
}
|
||||
|
||||
// Agent
|
||||
if (env.agent) {
|
||||
if (env.agent.name) env.agent.name = this.app.modelService.sanitizeInput(env.agent.name);
|
||||
if (env.agent.description) env.agent.description = this.app.modelService.sanitizeInput(env.agent.description);
|
||||
if (env.agent.systemInstruction) {
|
||||
env.agent.systemInstruction = env.agent.systemInstruction.map((s: string) => this.app.modelService.sanitizeInput(s));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public getEnvDetailsAsString(env: Env): string {
|
||||
return "Env details: " +
|
||||
"\nname: " + env.name +
|
||||
"\ndescription: " + env.description +
|
||||
"\n\ncompletion model: " +
|
||||
"\nname: " + (env.completion?.name || "") +
|
||||
"\nlocal start command: " + (env.completion?.localStartCommand || "") +
|
||||
"\nendpoint: " + (env.completion?.endpoint || "") +
|
||||
"\nmodel name for provider: " + (env.completion?.aiModel || "") +
|
||||
"\napi key required: " + (env.completion?.isKeyRequired || false) +
|
||||
"\n\nchat model: " +
|
||||
"\nname: " + (env.chat?.name || "") +
|
||||
"\nlocal start command: " + (env.chat?.localStartCommand || "") +
|
||||
"\nendpoint: " + (env.chat?.endpoint || "") +
|
||||
"\nmodel name for provider: " + (env.chat?.aiModel || "") +
|
||||
"\napi key required: " + (env.chat?.isKeyRequired || false) +
|
||||
"\n\nembeddings model: " +
|
||||
"\nname: " + (env.embeddings?.name || "") +
|
||||
"\nlocal start command: " + (env.embeddings?.localStartCommand || "") +
|
||||
"\nendpoint: " + (env.embeddings?.endpoint || "") +
|
||||
"\nmodel name for provider: " + (env.embeddings?.aiModel || "") +
|
||||
"\napi key required: " + (env.embeddings?.isKeyRequired || false) +
|
||||
"\n\ntools model: " +
|
||||
"\nname: " + (env.tools?.name || "") +
|
||||
"\nlocal start command: " + (env.tools?.localStartCommand || "") +
|
||||
"\nendpoint: " + (env.tools?.endpoint || "") +
|
||||
"\nmodel name for provider: " + (env.tools?.aiModel || "") +
|
||||
"\napi key required: " + (env.tools?.isKeyRequired || false) +
|
||||
"\n\nagent: " +
|
||||
"\nname: " + (env.agent?.name || "") +
|
||||
"\ndescription: " + (env.agent?.description || "") +
|
||||
"\n\ncompletions enabled: " + (env.complEnabled ?? "") +
|
||||
"\n\nrag enabled: " + (env.ragEnabled ?? "") +
|
||||
"\n\nenv start last: " + (env.envStartLastUsed ?? "");
|
||||
}
|
||||
|
||||
private getStandardQpList(list: Env[], prefix: string, lastEnvNumber: number = 0): QuickPickItem[] {
|
||||
const items: QuickPickItem[] = [];
|
||||
let i = lastEnvNumber;
|
||||
for (let elem of list) {
|
||||
i++;
|
||||
items.push({
|
||||
label: i + ". " + prefix + elem.name,
|
||||
description: elem.description,
|
||||
});
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,14 @@
|
|||
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;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { Application } from "../application";
|
|||
import { IAddStrategy, LlmModel, ModelTypeDetails } from "../types";
|
||||
import { Utils } from "../utils";
|
||||
import * as axios from "axios";
|
||||
import { HF_MODEL_TEMPLATES, SETTING_TO_MODEL_TYPE } from "../constants";
|
||||
import { ModelType, UI_TEXT_KEYS, HF_MODEL_TEMPLATES, SETTING_TO_MODEL_TYPE, MODEL_TYPE_CONFIG } from "../constants";
|
||||
|
||||
interface HuggingfaceModel {
|
||||
modelId: string;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@ import { QuickPickItem } from "vscode";
|
|||
import { Application } from "../application";
|
||||
import { IAddStrategy, LlmModel, ModelTypeDetails } from "../types";
|
||||
import { Utils } from "../utils";
|
||||
import { ModelType, UI_TEXT_KEYS, MODEL_TYPE_CONFIG } from "../constants";
|
||||
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";
|
||||
import { PREDEFINED_LISTS } from "../lists";
|
||||
|
||||
export class ModelService {
|
||||
|
||||
private app: Application;
|
||||
private strategies: Record<string, IAddStrategy>;
|
||||
|
||||
|
|
@ -19,8 +19,7 @@ export class ModelService {
|
|||
this.strategies = {
|
||||
local: this.app.localModelStrategy,
|
||||
external: this.app.externalModelStrategy,
|
||||
hf: this.app.hfModelStrategy,
|
||||
oaiComp: this.app.openAiCompModelStrategy
|
||||
hf: this.app.hfModelStrategy
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -32,7 +31,6 @@ export class ModelService {
|
|||
UI_TEXT_KEYS.addLocalCompletionModel,
|
||||
UI_TEXT_KEYS.addExternalCompletionModel,
|
||||
UI_TEXT_KEYS.addCompletionModelFromHuggingface,
|
||||
UI_TEXT_KEYS.addCompletionOpenAiCompModel,
|
||||
UI_TEXT_KEYS.viewCompletionModelDetails,
|
||||
UI_TEXT_KEYS.deleteCompletionModel,
|
||||
UI_TEXT_KEYS.exportCompletionModel,
|
||||
|
|
@ -44,7 +42,6 @@ export class ModelService {
|
|||
UI_TEXT_KEYS.addLocalChatModel,
|
||||
UI_TEXT_KEYS.addExternalChatModel,
|
||||
UI_TEXT_KEYS.addChatModelFromHuggingface,
|
||||
UI_TEXT_KEYS.addChatOpenAiCompModel,
|
||||
UI_TEXT_KEYS.viewChatModelDetails,
|
||||
UI_TEXT_KEYS.deleteChatModel,
|
||||
UI_TEXT_KEYS.exportChatModel,
|
||||
|
|
@ -56,7 +53,6 @@ export class ModelService {
|
|||
UI_TEXT_KEYS.addLocalEmbeddingsModel,
|
||||
UI_TEXT_KEYS.addExternalEmbeddingsModel,
|
||||
UI_TEXT_KEYS.addEmbeddingsModelFromHuggingface,
|
||||
UI_TEXT_KEYS.addEmbeddingsOpenAiCompModel,
|
||||
UI_TEXT_KEYS.viewEmbeddingsModelDetails,
|
||||
UI_TEXT_KEYS.deleteEmbeddingsModel,
|
||||
UI_TEXT_KEYS.exportEmbeddingsModel,
|
||||
|
|
@ -68,7 +64,6 @@ export class ModelService {
|
|||
UI_TEXT_KEYS.addLocalToolsModel,
|
||||
UI_TEXT_KEYS.addExternalToolsModel,
|
||||
UI_TEXT_KEYS.addToolsModelFromHuggingface,
|
||||
UI_TEXT_KEYS.addToolsOpenAiCompModel,
|
||||
UI_TEXT_KEYS.viewToolsModelDetails,
|
||||
UI_TEXT_KEYS.deleteToolsModel,
|
||||
UI_TEXT_KEYS.exportToolsModel,
|
||||
|
|
@ -82,14 +77,6 @@ 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);
|
||||
|
|
@ -112,9 +99,6 @@ export class ModelService {
|
|||
case 'addHf':
|
||||
await this.addModel(type, 'hf');
|
||||
break;
|
||||
case 'addOaiComp':
|
||||
await this.addModel(type, 'oaiComp');
|
||||
break;
|
||||
case 'delete':
|
||||
await this.deleteModel(details.modelsList, details.modelsListSettingName);
|
||||
break;
|
||||
|
|
@ -138,7 +122,6 @@ export class ModelService {
|
|||
addLocal: this.app.configuration.getUiText(UI_TEXT_KEYS[`addLocal${typeStr}Model` as keyof typeof UI_TEXT_KEYS]) ?? "",
|
||||
addExternal: this.app.configuration.getUiText(UI_TEXT_KEYS[`addExternal${typeStr}Model` as keyof typeof UI_TEXT_KEYS]) ?? "",
|
||||
addHf: this.app.configuration.getUiText(UI_TEXT_KEYS[`add${typeStr}ModelFromHuggingface` as keyof typeof UI_TEXT_KEYS]) ?? "",
|
||||
addOaiComp: this.app.configuration.getUiText(UI_TEXT_KEYS[`add${typeStr}OpenAiCompModel` as keyof typeof UI_TEXT_KEYS]) ?? "",
|
||||
view: this.app.configuration.getUiText(UI_TEXT_KEYS[`view${typeStr}ModelDetails` as keyof typeof UI_TEXT_KEYS]) ?? "",
|
||||
delete: this.app.configuration.getUiText(UI_TEXT_KEYS[`delete${typeStr}Model` as keyof typeof UI_TEXT_KEYS]) ?? "",
|
||||
export: this.app.configuration.getUiText(UI_TEXT_KEYS[`export${typeStr}Model` as keyof typeof UI_TEXT_KEYS]) ?? "",
|
||||
|
|
@ -146,7 +129,7 @@ export class ModelService {
|
|||
};
|
||||
}
|
||||
|
||||
selectModel = async (type: ModelType, modelsList: LlmModel[], shoudStartModel:boolean = true): Promise<LlmModel | undefined> => {
|
||||
selectModel = async (type: ModelType, modelsList: LlmModel[]): Promise<LlmModel | undefined> => {
|
||||
const details = this.getTypeDetails(type);
|
||||
let allModels = modelsList.concat(PREDEFINED_LISTS.get(type) as LlmModel[])
|
||||
let modelsItems: QuickPickItem[] = this.getModels(modelsList, "", true);
|
||||
|
|
@ -159,6 +142,9 @@ export class ModelService {
|
|||
["launch_tools", "endpoint_tools"]
|
||||
]);
|
||||
|
||||
// for (let mdl of PREDEFINED_LISTS.get(type) as LlmModel[]){
|
||||
// modelsItems.push({ label: (modelsItems.length + 1) + ". (predefined) " + mdl.name, description: mdl.localStartCommand ?? "" });
|
||||
// }
|
||||
modelsItems.push({ label: (modelsItems.length + 1) + ". Use settings", description: "" });
|
||||
|
||||
const selectedModelItem = await vscode.window.showQuickPick(modelsItems);
|
||||
|
|
@ -181,7 +167,18 @@ export class ModelService {
|
|||
model = allModels[index];
|
||||
}
|
||||
|
||||
if (shoudStartModel) await this.selectStartModel(model, type, details);
|
||||
// if (parseInt(selectedModelItem.label.split(". ")[0], 10) == modelsItems.length) {
|
||||
// model = {
|
||||
// name: "Use settings",
|
||||
// isKeyRequired: false,
|
||||
// endpoint: this.app.configuration[launchToEndpoint.get(details.launchSettingName) as keyof Configuration] as string,
|
||||
// localStartCommand: this.app.configuration[details.launchSettingName as keyof Configuration ] as string
|
||||
// } as LlmModel;
|
||||
// } else {
|
||||
// const index = parseInt(selectedModelItem.label.split(". ")[0], 10) - 1;
|
||||
// model = allModels[index] as LlmModel;
|
||||
// }
|
||||
await this.selectStartModel(model, type, details);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
|
@ -190,25 +187,14 @@ export class ModelService {
|
|||
|
||||
public async selectStartModel(model: LlmModel, type: ModelType, details: ModelTypeDetails) {
|
||||
await this.addApiKey(model);
|
||||
this.app.setSelectedModel(type, model);
|
||||
this.app.menu.setSelectedModel(type, model);
|
||||
|
||||
await details.killCmd();
|
||||
if (model.localStartCommand) await details.shellCmd(this.sanitizeCommand(model.localStartCommand ?? ""));
|
||||
await this.app.persistence.setValue(this.getSelectedProp(type), model);
|
||||
if (type == ModelType.Tools && model?.isKeyRequired !== undefined && model.isKeyRequired){
|
||||
const apiKey = this.app.persistence.getApiKey(model.endpoint??"");
|
||||
if (apiKey){
|
||||
this.app.configuration.axiosRequestConfigTools = {
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async addModel(type: ModelType, kind: 'local' | 'external' | 'hf' | 'oaiComp'): Promise<void> {
|
||||
public async addModel(type: ModelType, kind: 'local' | 'external' | 'hf'): Promise<void> {
|
||||
const details = this.getTypeDetails(type);
|
||||
const strategy = this.strategies[kind];
|
||||
if (strategy) {
|
||||
|
|
@ -245,7 +231,7 @@ export class ModelService {
|
|||
}
|
||||
|
||||
public async showModelDetails(model: LlmModel): Promise<void> {
|
||||
await Utils.showOkDialog("Model details: \n\n" + this.getDetails(model));
|
||||
await Utils.showOkDialog("Model details: " + this.getDetails(model));
|
||||
}
|
||||
|
||||
async exportModel(type: ModelType, modelsList: LlmModel[]): Promise<void> {
|
||||
|
|
@ -316,7 +302,7 @@ export class ModelService {
|
|||
|
||||
public async deselectModel(type: ModelType, details: ModelTypeDetails): Promise<void> {
|
||||
await details.killCmd();
|
||||
this.clearModel(type);
|
||||
this.app.menu.clearModel(type);
|
||||
}
|
||||
|
||||
getDetails(model: LlmModel): string {
|
||||
|
|
@ -392,91 +378,14 @@ export class ModelService {
|
|||
|
||||
sanitizeCommand = (command: string): string => {
|
||||
if (!command) return '';
|
||||
// TODO Consider escaping some chars: return command.trim().replace(/[`#$\<>\?\\|!{}()[\]^"]/g, '\\$&');
|
||||
return command.trim();
|
||||
return command.trim().replace(/[`#$&*;\<>\?\\|~!{}()[\]^"]/g, '\\$&');
|
||||
}
|
||||
|
||||
public sanitizeInput(input: string): string {
|
||||
return input ? input.trim() : '';
|
||||
}
|
||||
|
||||
clearModel = (type: ModelType) => {
|
||||
this.app.setSelectedModel(type, Application.emptyModel);
|
||||
this.app.setModelState(type, "");
|
||||
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 async selectAgentModel(modelType: ModelType, modelsList: LlmModel[]) {
|
||||
let model = await this.app.modelService.selectModel(modelType, modelsList, false);
|
||||
this.app.setAgentModel(model);
|
||||
}
|
||||
|
||||
public getEmptyModel(): LlmModel {
|
||||
return Application.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) {
|
||||
await Utils.suggestModelSelection(
|
||||
"Select a tools model or an env with tools model to use Llama Agent.",
|
||||
"After the tools model is loaded, try again opening llama agent.",
|
||||
"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.",
|
||||
this.app
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
else return true;
|
||||
}
|
||||
|
||||
periodicModelHealthUpdate = async () => {
|
||||
if (this.app.configuration.health_check_interval_s > 0) {
|
||||
if (this.app.configuration.health_check_compl_enabled && this.app.isComplModelSelected()) {
|
||||
await this.updateModelState(ModelType.Completion);
|
||||
}
|
||||
if (this.app.configuration.health_check_chat_enabled && this.app.isChatModelSelected()) {
|
||||
await this.updateModelState(ModelType.Chat);
|
||||
}
|
||||
if (this.app.configuration.health_check_embs_enabled && this.app.isEmbeddingsModelSelected()) {
|
||||
await this.updateModelState(ModelType.Embeddings);
|
||||
}
|
||||
if (this.app.configuration.health_check_tools_enabled && this.app.isToolsModelSelected()) {
|
||||
await this.updateModelState(ModelType.Tools);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async checkModelHealth(modelType: ModelType) {
|
||||
let healthState = await this.app.llamaServer.checkHealth(modelType, this.app.getModel(modelType));
|
||||
if (healthState.toLowerCase() == "ok" || healthState.toLowerCase() == "healthy") vscode.window.showInformationMessage(modelType.charAt(0).toUpperCase() + modelType.slice(1) + " model health is OK.");
|
||||
else vscode.window.showErrorMessage("Error with " + modelType + " model:" + healthState);
|
||||
this.app.setModelState(modelType, healthState);
|
||||
}
|
||||
|
||||
private async updateModelState(modelType: ModelType) {
|
||||
let healthState = await this.app.llamaServer.checkHealth(modelType, this.app.getModel(modelType));
|
||||
let currentHealthState = this.app.getModelState(modelType);
|
||||
if ((currentHealthState == "" || currentHealthState.toLocaleLowerCase() == "ok" || currentHealthState.toLocaleLowerCase() == "healthy")
|
||||
&& healthState.toLowerCase() != "ok" && healthState.toLowerCase() != "healthy") {
|
||||
vscode.window.showErrorMessage("Error with completion model:" + healthState);
|
||||
}
|
||||
this.app.setModelState(modelType, healthState.slice(0, 150));
|
||||
}
|
||||
// For selectModel, adjust to return model
|
||||
// In the code above, I have return model; at the end of if.
|
||||
// Yes.
|
||||
}
|
||||
|
|
@ -1,191 +0,0 @@
|
|||
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 { OPENAI_COMP_PROVIDERS, OpenAiProvidersKeys, SETTING_TO_MODEL_TYPE } from "../constants";
|
||||
|
||||
interface OpenAiCompModel {
|
||||
name: string;
|
||||
id: string;
|
||||
description: string;
|
||||
context_length: string;
|
||||
pricing: {
|
||||
completion: number;
|
||||
prompt: number;
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenAiCompModelStrategy implements IAddStrategy {
|
||||
private app: Application;
|
||||
|
||||
constructor(app: Application) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
add = async (details: ModelTypeDetails): Promise<void> => {
|
||||
const modelType = SETTING_TO_MODEL_TYPE[details.modelsListSettingName];
|
||||
const openAiCompProviders: QuickPickItem[] = [];
|
||||
for (const [key, value] of Object.entries(OPENAI_COMP_PROVIDERS)) {
|
||||
openAiCompProviders.push({
|
||||
label: key,
|
||||
description: value
|
||||
});
|
||||
}
|
||||
const selProvider = await vscode.window.showQuickPick(openAiCompProviders);
|
||||
if (selProvider && selProvider.label) {
|
||||
//if custom - ask for endpoint
|
||||
let endpoint = selProvider.description??""
|
||||
let isKeyRequired = true;
|
||||
if (selProvider.label == OpenAiProvidersKeys.Custom){
|
||||
endpoint = await vscode.window.showInputBox({
|
||||
placeHolder: 'endpoint (URL to the API) of an OpenAI compatible provider',
|
||||
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, isKeyRequired);
|
||||
if (models.length == 0) {
|
||||
vscode.window.showInformationMessage("No models are found.")
|
||||
return
|
||||
}
|
||||
for (let mdl of models) {
|
||||
providerModels.push({
|
||||
label: mdl.name,
|
||||
description: mdl.context_length + " context | $" + (mdl.pricing?.prompt*1000000).toFixed(2) + "/M input tokens | $" + (mdl.pricing?.completion*1000000).toFixed(2) + "/M output tokens",
|
||||
detail: mdl.description + " | "+ mdl.id,
|
||||
});
|
||||
}
|
||||
const selModel = await vscode.window.showQuickPick(providerModels);
|
||||
if (!selModel){
|
||||
vscode.window.showWarningMessage("No model is selected!")
|
||||
return;
|
||||
}
|
||||
let aiModel = selModel.detail
|
||||
if(aiModel) aiModel = aiModel.split("|").slice(-1)[0]?.trim()
|
||||
let provider = selProvider.label
|
||||
if (provider.endsWith("...")) provider = provider.slice(0,-3)
|
||||
let newModel: LlmModel = {
|
||||
name: provider + ": " + selModel.label,
|
||||
localStartCommand: "",
|
||||
endpoint: endpoint,
|
||||
aiModel: aiModel == "undefined" ? "" : aiModel??"",
|
||||
isKeyRequired: isKeyRequired
|
||||
};
|
||||
|
||||
const shouldAddModel = await Utils.confirmAction("You have entered:",
|
||||
this.getModelDetailsAsString(newModel) +
|
||||
"\n\nDo you want to add a model with these properties?"
|
||||
);
|
||||
|
||||
if (shouldAddModel) {
|
||||
let shouldOverwrite = false;
|
||||
[newModel.name, shouldOverwrite] = await this.getUniqueModelName(details.modelsList, newModel);
|
||||
if (!newModel.name) {
|
||||
vscode.window.showInformationMessage("The model was not added as the name was not provided.")
|
||||
return;
|
||||
}
|
||||
if (shouldOverwrite) {
|
||||
const index = details.modelsList.findIndex(model => model.name === newModel.name);
|
||||
if (index !== -1) {
|
||||
details.modelsList.splice(index, 1);
|
||||
}
|
||||
}
|
||||
details.modelsList.push(newModel);
|
||||
this.app.configuration.updateConfigValue(details.modelsListSettingName, details.modelsList);
|
||||
vscode.window.showInformationMessage("The model is added: " + newModel.name)
|
||||
const shouldSelect = await Utils.confirmAction("Do you want to select/start the newly added model?", "");
|
||||
if (shouldSelect) {
|
||||
await this.app.modelService.selectStartModel(newModel, modelType, details);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
return models;
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage("Error getting provider models: " + error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private sanitizeInput(input: string): string {
|
||||
return input ? input.trim() : '';
|
||||
}
|
||||
|
||||
private async getUniqueModelName(modelsList: LlmModel[], newModel: LlmModel): Promise<[string, boolean]> {
|
||||
let uniqueName = newModel.name;
|
||||
let shouldOverwrite = false;
|
||||
let modelSameName = modelsList.find(model => model.name === uniqueName);
|
||||
while (uniqueName && !shouldOverwrite && modelSameName !== undefined) {
|
||||
shouldOverwrite = await Utils.confirmAction("A model with the same name already exists. Do you want to overwrite the existing model?",
|
||||
"Existing model:\n" +
|
||||
this.getModelDetailsAsString(modelSameName) +
|
||||
"\n\nNew model:\n" +
|
||||
this.getModelDetailsAsString(newModel)
|
||||
);
|
||||
if (!shouldOverwrite) {
|
||||
uniqueName = (await vscode.window.showInputBox({
|
||||
placeHolder: 'a unique name for your new model',
|
||||
prompt: 'Enter a unique name for your new model. Leave empty to cancel entering.',
|
||||
value: newModel.name
|
||||
})) ?? "";
|
||||
uniqueName = this.sanitizeInput(uniqueName);
|
||||
if (uniqueName) modelSameName = modelsList.find(model => model.name === uniqueName);
|
||||
}
|
||||
}
|
||||
|
||||
return [uniqueName, shouldOverwrite]
|
||||
}
|
||||
|
||||
private getModelDetailsAsString(model: LlmModel): string {
|
||||
return "name: " + model.name +
|
||||
"\nlocal start command: " + model.localStartCommand +
|
||||
"\nendpoint: " + model.endpoint +
|
||||
"\nmodel name for provider: " + model.aiModel +
|
||||
"\napi key required: " + model.isKeyRequired
|
||||
}
|
||||
}
|
||||
15
src/test/extension.test.ts
Normal file
15
src/test/extension.test.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
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));
|
||||
// });
|
||||
// });
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
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();
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
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');
|
||||
}
|
||||
|
|
@ -1,184 +0,0 @@
|
|||
/// <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');
|
||||
});
|
||||
});
|
||||
|
|
@ -26,209 +26,25 @@ export class TextEditor {
|
|||
vscode.commands.executeCommand('setContext', 'textEditSuggestionVisible', visible);
|
||||
}
|
||||
|
||||
private escapeWebviewAttr(value: string): string {
|
||||
return value
|
||||
.replace(/&/g, '&')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
.replace(/</g, '<');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
let chatModel = this.app.getChatModel();
|
||||
if (!this.app.isChatModelSelected()) chatModel = this.app.getToolsModel();
|
||||
let chatModel = this.app.menu.getChatModel();
|
||||
if (!this.app.menu.isChatModelSelected()) chatModel = this.app.menu.getToolsModel();
|
||||
if (chatModel.endpoint) {
|
||||
const chatEndpoint = Utils.trimTrailingSlash(chatModel.endpoint)
|
||||
chatUrl = chatEndpoint ? chatEndpoint + "/" : "";
|
||||
}
|
||||
if (!chatUrl) {
|
||||
await Utils.suggestModelSelection(
|
||||
"Select a chat or tools model or an env with chat or tools model to edit code with AI.",
|
||||
"After the chat model is loaded, try again using Edit with AI.",
|
||||
"No endpoint for the chat model. Select an env with chat model or enter the endpoint of a running llama.cpp server with chat model in setting endpoint_chat.",
|
||||
this.app
|
||||
);
|
||||
return
|
||||
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();
|
||||
vscode.window.showInformationMessage("After the chat model is loaded, try again using Edit with AI.")
|
||||
return;
|
||||
} else {
|
||||
vscode.window.showErrorMessage("No endpoint for the chat model. Select an env with chat model or enter the endpoint of a running llama.cpp server with chat model in setting endpoint_chat. ")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (editor.selection.isEmpty) {
|
||||
|
|
@ -250,7 +66,12 @@ export class TextEditor {
|
|||
const contextRange = new vscode.Range(startLine, 0, endLine, editor.document.lineAt(endLine).text.length);
|
||||
const context = editor.document.getText(contextRange);
|
||||
|
||||
const prompt = await this.showMultilineEditPrompt();
|
||||
// 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
|
||||
});
|
||||
|
||||
if (!prompt) {
|
||||
return;
|
||||
|
|
@ -276,7 +97,7 @@ export class TextEditor {
|
|||
vscode.window.showInformationMessage('No suggestions available');
|
||||
return;
|
||||
}
|
||||
this.currentSuggestion = Utils.removeFirstAndLastLinesIfBackticks(data.choices[0].message.content.trim());
|
||||
this.currentSuggestion = this.removeFirstAndLastLinesIfBackticks(data.choices[0].message.content.trim());
|
||||
this.currentSuggestion = Utils.addLeadingSpaces(this.currentSuggestion, this.removedSpaces)
|
||||
// Show the suggestion in a diff view
|
||||
await this.showDiffView(editor, this.currentSuggestion);
|
||||
|
|
@ -292,7 +113,21 @@ export class TextEditor {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private removeFirstAndLastLinesIfBackticks(input: string): string {
|
||||
const lines = input.split('\n'); // Split the string into lines
|
||||
|
||||
// Remove the first line if it starts with ```
|
||||
if (lines[0]?.trim().startsWith('```')) {
|
||||
lines.shift(); // Remove the first line
|
||||
}
|
||||
|
||||
// Remove the last line if it starts with ```
|
||||
if (lines[lines.length - 1]?.trim().startsWith('```')) {
|
||||
lines.pop(); // Remove the last line
|
||||
}
|
||||
|
||||
return lines.join('\n'); // Join the remaining lines back into a string
|
||||
}
|
||||
|
||||
private async showDiffView(editor: vscode.TextEditor, suggestion: string) {
|
||||
// Get context before and after the selection
|
||||
|
|
|
|||
262
src/tools.ts
262
src/tools.ts
|
|
@ -4,8 +4,6 @@ import {Utils} from "./utils";
|
|||
import path from "path";
|
||||
import fs from 'fs';
|
||||
import { Plugin } from './plugin';
|
||||
import { UI_TEXT_KEYS } from "./constants";
|
||||
import { Chat, Agent } from "./types";
|
||||
|
||||
type ToolsMap = Map<string, (...args: any[]) => any>;
|
||||
|
||||
|
|
@ -13,7 +11,7 @@ export class Tools {
|
|||
private app: Application;
|
||||
toolsFunc: ToolsMap = new Map();
|
||||
toolsFuncDesc: ToolsMap = new Map();
|
||||
private tools: any[] = [];
|
||||
tools: any[] = [];
|
||||
vscodeTools: any[] = [];
|
||||
vscodeToolsSelected: Map<string, boolean> = new Map();
|
||||
|
||||
|
|
@ -31,9 +29,6 @@ export class Tools {
|
|||
this.toolsFunc.set("custom_tool", this.customTool)
|
||||
this.toolsFunc.set("custom_eval_tool", this.customEvalTool)
|
||||
this.toolsFunc.set("llama_vscode_help", this.llamaVscodeHelp)
|
||||
this.toolsFunc.set("update_todo_list", this.updateTodoList)
|
||||
this.toolsFunc.set("delegate_task", this.delegateTask)
|
||||
this.toolsFunc.set("create_agent", this.createAgent)
|
||||
this.toolsFuncDesc.set("run_terminal_command", this.runTerminalCommandDesc);
|
||||
this.toolsFuncDesc.set("search_source", this.searchSourceDesc)
|
||||
this.toolsFuncDesc.set("read_file", this.readFileDesc)
|
||||
|
|
@ -46,10 +41,7 @@ export class Tools {
|
|||
this.toolsFuncDesc.set("custom_tool", this.customToolDesc)
|
||||
this.toolsFuncDesc.set("custom_eval_tool", this.customEvalToolDesc)
|
||||
this.toolsFuncDesc.set("llama_vscode_help", this.llamaVscodeHelpDesc)
|
||||
this.toolsFuncDesc.set("update_todo_list", this.updateTodoListDesc)
|
||||
this.toolsFuncDesc.set("update_todo_list", this.updateTodoListDesc);
|
||||
this.toolsFuncDesc.set("delegate_task", this.delegateTaskDesc)
|
||||
this.toolsFuncDesc.set("create_agent ", this.createAgentDesc);
|
||||
|
||||
}
|
||||
|
||||
public runTerminalCommand = async (args: string ) => {
|
||||
|
|
@ -110,48 +102,21 @@ export class Tools {
|
|||
uri = vscode.Uri.file(absolutePath);
|
||||
const document = await vscode.workspace.openTextDocument(uri)
|
||||
if (params.should_read_entire_file) return document.getText()
|
||||
|
||||
// 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) {
|
||||
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) {
|
||||
return 'Invalid line range';
|
||||
}
|
||||
|
||||
// Apply 250-line limit using the converted 0-based firstLine
|
||||
lastLine = Math.min(lastLine, firstLine + 249)
|
||||
let lastLine = Math.min(params.last_line_inclusive - 1, params.first_line + 249, document.lineCount -1)
|
||||
|
||||
// Create range from first line's start to last line's end
|
||||
const startPos = new vscode.Position(Math.max(firstLine, 0), 0);
|
||||
const startPos = new vscode.Position(Math.max(params.first_line -1, 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) {
|
||||
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
|
||||
return "File not found: " + filePath;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -223,7 +188,6 @@ export class Tools {
|
|||
return `File not found at ${filePath}`;
|
||||
}
|
||||
fs.unlinkSync(absolutePath);
|
||||
this.app.chatContext.removeDocument(absolutePath)
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
return `Failed to delete file at ${filePath}: ${error.message}`;
|
||||
|
|
@ -261,11 +225,9 @@ export class Tools {
|
|||
let changes = params.input;
|
||||
|
||||
if (params.input == undefined) return "The input is not provided."
|
||||
|
||||
let filePath = this.getFilePath(params.input);
|
||||
if (!filePath) return "The file is not provided.";
|
||||
|
||||
|
||||
if (!fs.existsSync(filePath)) return "The does not exist: " + filePath;
|
||||
try {
|
||||
if (!this.app.configuration.tool_permit_file_changes){
|
||||
let [yesApply, yesDontAsk] = await Utils.showYesYesdontaskNoDialog("Do you permit file " + filePath + " to be changed?")
|
||||
|
|
@ -275,11 +237,8 @@ export class Tools {
|
|||
}
|
||||
if (!yesApply) return Utils.MSG_NO_UESR_PERMISSION;
|
||||
}
|
||||
let resultEdit = await Utils.applyEdits(changes)
|
||||
if (resultEdit == UI_TEXT_KEYS.fileUpdated && this.app.configuration.rag_enabled && fs.existsSync(filePath)) {
|
||||
this.app.chatContext.udpateFileIndexing(filePath, fs.readFileSync(filePath, 'utf-8'))
|
||||
}
|
||||
return resultEdit;
|
||||
await Utils.applyEdits(changes)
|
||||
return "The file is updated ";
|
||||
} catch (error) {
|
||||
console.error('Error changes since last commit:', error);
|
||||
throw error;
|
||||
|
|
@ -289,7 +248,7 @@ export class Tools {
|
|||
public editFileDesc = async (args: string) => {
|
||||
let params = JSON.parse(args);
|
||||
let diffText = params.input;
|
||||
if (!diffText) return "Parameter input not found."
|
||||
if (!diffText) return "EditFile Desc - parameter input not found."
|
||||
|
||||
let filePath = this.getFilePath(diffText);
|
||||
|
||||
|
|
@ -383,113 +342,7 @@ export class Tools {
|
|||
public llamaVscodeHelpDesc = async (args: string) => {
|
||||
return "llama_vscode_help tool is executed. "
|
||||
}
|
||||
|
||||
public updateTodoList = async (args: string) => {
|
||||
let params = JSON.parse(args);
|
||||
|
||||
if (params.todos == undefined) return "The todos are not provided."
|
||||
|
||||
let filePath = Utils.getTodosFilePath();
|
||||
|
||||
try {
|
||||
fs.writeFileSync(filePath, params.todos, 'utf8');
|
||||
} catch (error) {
|
||||
return `Error creating/updating todos`
|
||||
}
|
||||
|
||||
return "The todos are created/updated."
|
||||
}
|
||||
|
||||
public updateTodoListDesc = async (args: string) => {
|
||||
let ret = "update_todo_list tool is executed. \n\n"
|
||||
let params = JSON.parse(args);
|
||||
if (params.todos) ret += params.todos.split(/\r?\n/).join(" \n")
|
||||
return ret
|
||||
}
|
||||
|
||||
public delegateTask = async (args: string) => {
|
||||
let params = JSON.parse(args);
|
||||
let finalAnswer = "No answer from the subagent.";
|
||||
if (params.subagent_name) {
|
||||
// store current agent
|
||||
await this.app.llamaAgent.updateChat()
|
||||
let parentChat = this.app.getChat();
|
||||
parentChat.defaultAgent = this.app.getAgent();
|
||||
let subagent: Agent = this.app.configuration.agents_list.find(agent => agent.name == params.subagent_name)
|
||||
if (!subagent) return "No subagent found with name " + params.subagent_name;
|
||||
this.app.llamaAgent.resetContext();
|
||||
let newChatName = "delegate_task" + Date.now()
|
||||
let newSubagent: Agent = { ...subagent };
|
||||
if (subagent.tools){
|
||||
// clone the tools to avoid changing the original agent
|
||||
newSubagent.tools = [...subagent.tools];
|
||||
} else {
|
||||
newSubagent.tools = [];
|
||||
}
|
||||
newSubagent.tools.push("ask_user")
|
||||
// The goal is to get the answer from the subagent in one tools loop - so use a tool call if user input is needed
|
||||
newSubagent.systemInstruction.push("For questions to the user, please use the tool 'ask_user'.")
|
||||
let newChat: Chat = {
|
||||
name: newChatName,
|
||||
id: newChatName,
|
||||
messages: [],
|
||||
defaultAgent: newSubagent,
|
||||
log: "subagent: " + params.subagent_name + " \n \n"
|
||||
}
|
||||
|
||||
await this.app.chatService.selectUpdateChat(newChat)
|
||||
|
||||
if (params.task) {
|
||||
finalAnswer = await this.app.llamaAgent.askAgent(params.task)
|
||||
} else {
|
||||
return "No task provided."
|
||||
}
|
||||
await this.app.chatService.selectUpdateChat(parentChat)
|
||||
this.app.llamaWebviewProvider.setState("AI is working")
|
||||
} else {
|
||||
return "No subagent name provided."
|
||||
}
|
||||
|
||||
return finalAnswer
|
||||
}
|
||||
|
||||
public delegateTaskDesc = async (args: string) => {
|
||||
let ret = "delegate_task tool is executed. \n\n"
|
||||
let params = JSON.parse(args);
|
||||
if (params.task && params.subagent_name) ret += "subagent: " + params.subagent_name + "\ntask: " + params.task
|
||||
return ret.split(/\r?\n/).join(" \n")
|
||||
}
|
||||
|
||||
public createAgent = async (args: string) => {
|
||||
let params = JSON.parse(args);
|
||||
let finalAnswer = "The agent is created";
|
||||
|
||||
if (params.agent_json) {
|
||||
let receivedAgent = JSON.parse(params.agent_json)
|
||||
let newAgent:Agent = {
|
||||
name: receivedAgent.name,
|
||||
description: receivedAgent.description,
|
||||
subagentEnabled: receivedAgent.subagentEnabled??false,
|
||||
systemInstruction: receivedAgent.systemInstruction.split(/\r?\n/)
|
||||
}
|
||||
if (receivedAgent.tools) {
|
||||
newAgent.tools = receivedAgent.tools.split(",")
|
||||
}
|
||||
// TODO check if one more parsing of agent_json is needed
|
||||
await this.app.agentService.addUpdateAgent(newAgent)
|
||||
} else {
|
||||
return "No agent provided."
|
||||
}
|
||||
|
||||
return finalAnswer
|
||||
}
|
||||
|
||||
public createAgentDesc = async (args: string) => {
|
||||
let ret = "create_agent tool is executed. \n\n"
|
||||
// let params = JSON.parse(args);
|
||||
// if (params.task && params.subagent_name) ret += "subagent: " + params.subagent_name + "\ntask: " + params.task
|
||||
return ret.split(/\r?\n/).join(" \n")
|
||||
}
|
||||
|
||||
public init = () => {
|
||||
this.tools = [
|
||||
|
|
@ -650,8 +503,8 @@ export class Tools {
|
|||
"description": "Gets the files changes since last commit",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": [],
|
||||
"required": [
|
||||
],
|
||||
},
|
||||
"strict": true
|
||||
}
|
||||
|
|
@ -751,70 +604,6 @@ export class Tools {
|
|||
}
|
||||
}
|
||||
] : []),
|
||||
...(this.app.configuration.tool_update_todo_list_enabled ? [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "update_todo_list",
|
||||
"description": this.app.prompts.TOOL_UPDATE_TODO_LIST_DESCRIPTION,
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"todos": {
|
||||
"description": this.app.prompts.TOOL_UPDATE_TODO_LIST_PARAMETER_DESCRIPTION,
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"required": [],
|
||||
},
|
||||
"strict": true
|
||||
}
|
||||
}
|
||||
] : []),
|
||||
...(this.app.configuration.tool_create_agent_enabled ? [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "create_agent",
|
||||
"description": this.app.prompts.TOOL_CREATE_AGENT_DESCRIPTION,
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"agent_json": {
|
||||
"description": this.app.prompts.PROPERTY_AGENT_JSON_DESCRIPTION,
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"required": [],
|
||||
},
|
||||
"strict": true
|
||||
}
|
||||
}
|
||||
] : []),
|
||||
...(this.app.configuration.tool_delegate_task_enabled ? [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "delegate_task",
|
||||
"description": this.app.prompts.TOOL_DELEGATE_TASK_DESCRIPTION,
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"subagent_name": {
|
||||
"description": "Name of the subagent to invoke. Must be one of the available subagents listed in the system prompt.",
|
||||
"type": "string",
|
||||
},
|
||||
"task": {
|
||||
"description": "Description of the task to be delegated to the subagent.",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"required": [],
|
||||
},
|
||||
"strict": true
|
||||
}
|
||||
}
|
||||
] : []),
|
||||
]
|
||||
|
||||
for (let tool of this.app.configuration.tools_custom){
|
||||
|
|
@ -837,10 +626,6 @@ export class Tools {
|
|||
|
||||
}
|
||||
|
||||
getTools = () => {
|
||||
return this.tools;
|
||||
}
|
||||
|
||||
selectTools = async () => {
|
||||
// Define items with initial selection state
|
||||
const toolItems: vscode.QuickPickItem[] = []
|
||||
|
|
@ -936,27 +721,10 @@ export class Tools {
|
|||
let blockParts = Utils.extractConflictParts("```diff" + blocks.slice(1)[0]);
|
||||
filePath = blockParts[0].trim();
|
||||
} else {
|
||||
if (diffText.length > 0){
|
||||
if (diffText.startsWith("```\n")) diffText = diffText.slice(5)
|
||||
filePath = Utils.extractConflictParts("```diff\n" + diffText)[0].trim()
|
||||
}
|
||||
if (diffText.length > 0) filePath = Utils.extractConflictParts("```diff\n" + diffText)[0].trim()
|
||||
else return "";
|
||||
}
|
||||
|
||||
// Workaround for ClaudCode project file format - get only the relative path to the file
|
||||
if (filePath.includes(" ## ")) filePath = filePath.split(" ## ")[1];
|
||||
if (filePath.startsWith("## ")) filePath = filePath.slice(3);
|
||||
|
||||
let absolutePath = filePath;
|
||||
if (!path.isAbsolute(filePath)) {
|
||||
if (!vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length === 0) {
|
||||
return "File not found: " + filePath;
|
||||
}
|
||||
const workspaceRoot = vscode.workspace.workspaceFolders[0].uri.fsPath;
|
||||
absolutePath = path.join(workspaceRoot, filePath);
|
||||
}
|
||||
|
||||
return absolutePath;
|
||||
return filePath;
|
||||
}
|
||||
|
||||
private async indexFilesIfNeeded() {
|
||||
|
|
|
|||
|
|
@ -180,22 +180,4 @@ 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"],
|
||||
["Add completion model from OpenAI compatible provider...", "Добавяне на модел за допълване от съвместим с OpenAI доставчик...", "Ergänzungsmodell von einem mit OpenAI kompatiblen Anbieter hinzufügen...", "Добавить модель завершения от совместимого с OpenAI поставщика...", "Añadir modelo de finalización de un proveedor compatible con OpenAI...", "添加来自与 OpenAI 兼容提供程序的补全模型...", "Ajouter un modèle de complétion d'un fournisseur compatible OpenAI..."],
|
||||
["Add chat model from OpenAI compatible provider...", "Добавяне на чат модел от съвместим с OpenAI доставчик...", "Chat-Modell von einem mit OpenAI kompatiblen Anbieter hinzufügen...", "Добавить чат-модель от совместимого с OpenAI поставщика...", "Añadir modelo de chat de un proveedor compatible con OpenAI...", "添加来自与 OpenAI 兼容提供程序的聊天模型...", "Ajouter un modèle de chat d'un fournisseur compatible OpenAI..."],
|
||||
["Add embeddings model from OpenAI compatible provider...", "Добавяне на модел за ембединг от съвместим с OpenAI доставчик...", "Einbettungsmodell von einem mit OpenAI kompatiblen Anbieter hinzufügen...", "Добавить модель эмбеддингов от совместимого с OpenAI поставщика...", "Añadir modelo de incrustaciones de un proveedor compatible con OpenAI...", "添加来自与 OpenAI 兼容提供程序的嵌入模型...", "Ajouter un modèle d'incorporations d'un fournisseur compatible OpenAI..."],
|
||||
["Add tools model from OpenAI compatible provider...", "Добавяне на модел за инструменти от съвместим с OpenAI доставчик...", "Werkzeugmodell von einem mit OpenAI kompatiblen Anbieter hinzufügen...", "Добавить модель инструментов от совместимого с OpenAI поставщика...", "Añadir modelo de herramientas de un proveedor compatible con OpenAI...", "添加来自与 OpenAI 兼容提供程序的工具模型...", "Ajouter un modèle d'outils d'un fournisseur compatible OpenAI..."],
|
||||
["Edit agent...", "Редактиране на агент...", "Agent bearbeiten...", "Редактировать агента...", "Editar agente...", "编辑代理...", "Modifier l'agent..."],
|
||||
["Copy agent...", "Копиране на агент...", "Agent kopieren...", "Копировать агента...", "Copiar agente...", "复制代理...", "Copier l'agent..."],
|
||||
["Edit multiple files with AI", "Редактиране на множество файлове с изкуствен интелект", "Mehrere Dateien mit KI bearbeiten", "Редактировать несколько файлов с ИИ", "Editar múltiples archivos con IA", "使用人工智能编辑多个文件", "Modifier plusieurs fichiers avec IA"],
|
||||
["Asks for glob pattern and prompt and edits with AI the files, which match the glob pattern with the provided prompt.", "Пита за шаблон за обхват и инструкция, след което редактира с изкуствен интелект файловете, които отговарят на шаблона, със зададената инструкция.", "Fragt nach einem Glob-Muster und einer Eingabeaufforderung und bearbeitet mit KI die Dateien, die dem Glob-Muster mit der bereitgestellten Eingabeaufforderung entsprechen.", "Запрашивает шаблон glob и подсказку, а затем редактирует с помощью ИИ файлы, соответствующие шаблону, с предоставленной подсказкой.", "Pide un patrón glob y un mensaje, y edita con IA los archivos que coinciden con el patrón glob con el mensaje proporcionado.", "请求输入通配符模式和提示词,然后使用人工智能编辑符合该通配符模式的文件,并根据提供的提示词进行修改。", "Demande un motif glob et une invite, puis modifie avec IA les fichiers correspondant au motif glob avec l'invite fournie."],
|
||||
["This will remove the current conversation. Do you want to continue?", "Това ще премахне текущия разговор. Искате ли да продължите?", "Dadurch wird die aktuelle Unterhaltung gelöscht. Möchten Sie fortfahren?", "Это удалит текущий разговор. Хотите продолжить?", "Esto eliminará la conversación actual. ¿Quieres continuar?", "这将删除当前对话。是否要继续?", "Cela supprimera la conversation en cours. Voulez-vous continuer ?"],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -60,9 +60,7 @@ export interface Env {
|
|||
export interface Agent {
|
||||
name: string,
|
||||
description?: string,
|
||||
subagentEnabled?: boolean,
|
||||
systemInstruction: string[],
|
||||
toolsModel?: LlmModel,
|
||||
systemInstruction: string[]
|
||||
tools?: string[]
|
||||
}
|
||||
|
||||
|
|
|
|||
126
src/utils.ts
126
src/utils.ts
|
|
@ -1,4 +1,4 @@
|
|||
import vscode, { QuickPickItem, Uri } from "vscode";
|
||||
import vscode, { Uri } from "vscode";
|
||||
import { exec } from 'child_process';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
|
@ -7,8 +7,6 @@ import pm from 'picomatch'
|
|||
import * as https from 'https';
|
||||
import * as http from 'http';
|
||||
import { URL } from 'url';
|
||||
import { Application } from "./application";
|
||||
import { UI_TEXT_KEYS } from "./constants";
|
||||
|
||||
|
||||
interface BM25Stats {
|
||||
|
|
@ -322,14 +320,14 @@ export class Utils {
|
|||
return choice === 'Yes';
|
||||
}
|
||||
|
||||
static showUserChoiceDialog = async (message: string, acceptLabel: string): Promise<boolean> => {
|
||||
static showUserChoiceDialog = async (message: string, acceptLable: string): Promise<boolean> => {
|
||||
const choice = await vscode.window.showInformationMessage(
|
||||
"llama-vscode \n\n" + message,
|
||||
{ modal: true }, // Makes the dialog modal (blocks interaction until resolved)
|
||||
acceptLabel
|
||||
acceptLable
|
||||
);
|
||||
|
||||
return choice === acceptLabel;
|
||||
return choice === acceptLable;
|
||||
}
|
||||
|
||||
static showYesYesdontaskNoDialog = async (message: string): Promise<[boolean, boolean]> => {
|
||||
|
|
@ -344,18 +342,6 @@ export class Utils {
|
|||
return [choice === 'Yes' || choice === "Yes, don't ask again", choice === "Yes, don't ask again"];
|
||||
}
|
||||
|
||||
static showYesNoNodontAskDialog = async (message: string, acceptLabel: string): Promise<[boolean, boolean]> => {
|
||||
const choice = await vscode.window.showInformationMessage(
|
||||
"llama-vscode \n\n" + message,
|
||||
{ modal: true }, // Makes the dialog modal (blocks interaction until resolved)
|
||||
acceptLabel,
|
||||
'No',
|
||||
"No, don't ask again"
|
||||
);
|
||||
|
||||
return [choice === acceptLabel, choice === "No, don't ask again"];
|
||||
}
|
||||
|
||||
static showOkDialog = async (message: string) => {
|
||||
const choice = await vscode.window.showInformationMessage(
|
||||
"llama-vscode \n\n" + message,
|
||||
|
|
@ -557,9 +543,8 @@ export class Utils {
|
|||
return currentContent;
|
||||
}
|
||||
|
||||
static applyEdits = async (diffText: string): Promise<string> => {
|
||||
static applyEdits = async (diffText: string) => {
|
||||
// Extract edit blocks from the diff-fenced format
|
||||
let ret = UI_TEXT_KEYS.fileUpdated as string;
|
||||
let editBlocks: string[][] = [];
|
||||
if (!diffText) return "Edit file: The input parameter is missing!";
|
||||
const blocks = diffText.split("```diff")
|
||||
|
|
@ -569,7 +554,7 @@ export class Utils {
|
|||
|
||||
if (editBlocks.length === 0) {
|
||||
if (diffText.length > 0) editBlocks.push(Utils.extractConflictParts("```diff\n" + diffText))
|
||||
else return "Edit file: The input parameter is missing or incorrect format!";
|
||||
else return "";
|
||||
}
|
||||
|
||||
for (const block of editBlocks) {
|
||||
|
|
@ -591,32 +576,23 @@ export class Utils {
|
|||
const workspaceRoot = vscode.workspace.workspaceFolders[0].uri.fsPath;
|
||||
absolutePath = path.join(workspaceRoot, filePath);
|
||||
}
|
||||
try {
|
||||
const fileExists = await fs.promises.access(absolutePath).then(() => true).catch(() => false);
|
||||
if (!fileExists){
|
||||
await fs.promises.mkdir(path.dirname(absolutePath), { recursive: true });
|
||||
await fs.promises.writeFile(absolutePath, result);
|
||||
}
|
||||
// Ensure only \n is used for new line
|
||||
result = (await fs.promises.readFile(absolutePath, 'utf-8')).split(/\r?\n/).join("\n");
|
||||
// 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);
|
||||
} else {
|
||||
ret = "Error edititing file " + filePath + " - " + "The search text is not found in the file.";
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) ret = "Error edititing file " + filePath + " - " + error.message;
|
||||
else ret = "Error edititing file " + filePath + " - " + error;
|
||||
// Ensure only \n is used for new line
|
||||
const fileExists = await fs.promises.access(absolutePath).then(() => true).catch(() => false);
|
||||
if (!fileExists){
|
||||
await fs.promises.mkdir(path.dirname(absolutePath), { recursive: true });
|
||||
await fs.promises.writeFile(absolutePath, result);
|
||||
}
|
||||
result = (await fs.promises.readFile(absolutePath, 'utf-8')).split(/\r?\n/).join("\n");
|
||||
// Handle empty search text case
|
||||
if (searchText.trim() === '') {
|
||||
result += '\n' + replaceText;
|
||||
} else if (result.includes(searchText)) {
|
||||
result = result.split(searchText).join(replaceText);
|
||||
}
|
||||
|
||||
await fs.promises.writeFile(absolutePath, result);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
static extractConflictParts = (input: string): [string, string, string] => {
|
||||
|
|
@ -866,64 +842,4 @@ 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;
|
||||
}
|
||||
|
||||
static suggestModelSelection = async (choiceMsg: string, yesMsg: string, noMsg: string, app: Application) => {
|
||||
const shouldSelectModel = await Utils.showUserChoiceDialog(choiceMsg, "Select");
|
||||
if (shouldSelectModel) {
|
||||
app.llamaWebviewProvider.showEnvView();
|
||||
vscode.window.showInformationMessage(yesMsg);
|
||||
} else {
|
||||
vscode.window.showErrorMessage(noMsg);
|
||||
}
|
||||
}
|
||||
|
||||
static removeFirstAndLastLinesIfBackticks = (input: string): string => {
|
||||
const lines = input.split('\n'); // Split the string into lines
|
||||
|
||||
// Remove the first line if it starts with ```
|
||||
if (lines[0]?.trim().startsWith('```')) {
|
||||
lines.shift(); // Remove the first line
|
||||
}
|
||||
|
||||
// Remove the last line if it starts with ```
|
||||
if (lines[lines.length - 1]?.trim().startsWith('```')) {
|
||||
lines.pop(); // Remove the last line
|
||||
}
|
||||
|
||||
return lines.join('\n'); // Join the remaining lines back into a string
|
||||
}
|
||||
|
||||
static getTodosFilePath = () => {
|
||||
let filePath = "";
|
||||
const TODO_FILE = '.llama-vscode-todos.md';
|
||||
if (!vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length === 0) {
|
||||
filePath = TODO_FILE;
|
||||
} else {
|
||||
const workspaceRoot = vscode.workspace.workspaceFolders[0].uri.fsPath;
|
||||
filePath = path.join(workspaceRoot, TODO_FILE);
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
|
||||
static getWorkspaceFolder = () => {
|
||||
const workspaceFolders = vscode.workspace.workspaceFolders;
|
||||
if (!vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length === 0) {
|
||||
return "";
|
||||
} else {
|
||||
return vscode.workspace.workspaceFolders[0].uri.fsPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
77
src/vscode-lm-chat-shim.d.ts
vendored
77
src/vscode-lm-chat-shim.d.ts
vendored
|
|
@ -1,77 +0,0 @@
|
|||
// Temporary shim for VS Code LM chat-provider typings.
|
||||
// Some @types/vscode versions ship parts of the LM API behind proposal typings.
|
||||
// This keeps `tsc` happy while still targeting the runtime VS Code API.
|
||||
|
||||
import type * as vscode from 'vscode';
|
||||
|
||||
declare module 'vscode' {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
export namespace lm {
|
||||
function registerLanguageModelChatProvider(
|
||||
vendor: string,
|
||||
provider: LanguageModelChatProvider
|
||||
): vscode.Disposable;
|
||||
}
|
||||
|
||||
export interface PrepareLanguageModelChatModelOptions {}
|
||||
|
||||
export interface LanguageModelChatCapabilities {
|
||||
toolCalling?: boolean;
|
||||
imageInput?: boolean;
|
||||
}
|
||||
|
||||
export interface LanguageModelChatInformation {
|
||||
id: string;
|
||||
name: string;
|
||||
family?: string;
|
||||
version?: string;
|
||||
maxInputTokens?: number;
|
||||
maxOutputTokens?: number;
|
||||
capabilities?: LanguageModelChatCapabilities;
|
||||
}
|
||||
|
||||
export type LanguageModelChatMessagePart = unknown | LanguageModelTextPart;
|
||||
|
||||
export interface LanguageModelChatRequestMessage {
|
||||
role: LanguageModelChatMessageRole;
|
||||
content: readonly LanguageModelChatMessagePart[];
|
||||
}
|
||||
|
||||
export interface ProvideLanguageModelChatResponseOptions {
|
||||
tools?: readonly LanguageModelToolInformation[];
|
||||
modelOptions?: {
|
||||
temperature?: number;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export type LanguageModelResponsePart =
|
||||
| LanguageModelTextPart
|
||||
| LanguageModelToolCallPart
|
||||
| unknown;
|
||||
|
||||
export interface LanguageModelChatProvider {
|
||||
onDidChangeLanguageModelChatInformation?: vscode.Event<void>;
|
||||
|
||||
provideLanguageModelChatInformation(
|
||||
options: PrepareLanguageModelChatModelOptions,
|
||||
token: vscode.CancellationToken
|
||||
): vscode.ProviderResult<LanguageModelChatInformation[]>;
|
||||
|
||||
provideLanguageModelChatResponse(
|
||||
model: LanguageModelChatInformation,
|
||||
messages: readonly LanguageModelChatRequestMessage[],
|
||||
options: ProvideLanguageModelChatResponseOptions,
|
||||
progress: vscode.Progress<LanguageModelResponsePart>,
|
||||
token: vscode.CancellationToken
|
||||
): vscode.ProviderResult<void>;
|
||||
|
||||
provideTokenCount(
|
||||
model: LanguageModelChatInformation,
|
||||
text: string | LanguageModelChatRequestMessage,
|
||||
token: vscode.CancellationToken
|
||||
): vscode.ProviderResult<number>;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { AgentView, AIRunnerView, AddEnvView, AgentEditor } from './components';
|
||||
import { AgentView, AIRunnerView, AddEnvView } from './components';
|
||||
import { vscode } from './types/vscode';
|
||||
|
||||
interface AppProps { }
|
||||
|
|
@ -9,7 +9,6 @@ const App: React.FC<AppProps> = () => {
|
|||
const noModelSelected = 'No model selected';
|
||||
const noAgentSelected = 'No agent selected';
|
||||
const noViewSet = 'agent';
|
||||
const viewAgentEditor = 'agenteditor'
|
||||
const viewAiRunner = "airunner"
|
||||
const viewAddEnv = "addenv"
|
||||
|
||||
|
|
@ -24,9 +23,6 @@ const App: React.FC<AppProps> = () => {
|
|||
const [currentToolsModel, setCurrentToolsModel] = useState<string>(
|
||||
initialState.currentToolsModel || noModelSelected
|
||||
);
|
||||
const [currentAgentModel, setCurrentAgentModel] = useState<string>(
|
||||
initialState.currentAgentModel || noModelSelected
|
||||
);
|
||||
const [currentChatModel, setCurrentChatModel] = useState<string>(
|
||||
initialState.currentChatModel || noModelSelected
|
||||
);
|
||||
|
|
@ -49,9 +45,6 @@ const App: React.FC<AppProps> = () => {
|
|||
initialState.view || noViewSet
|
||||
);
|
||||
const [contextFiles, setContextFiles] = useState<Map<string, string>>(new Map());
|
||||
const [imagePath, setContextImage] = useState<string>("");
|
||||
const [agentEditTools, setAgentEditTools] = useState<Map<string, string>>(new Map());
|
||||
const [agentEditDetails, setAgentEditDetails] = useState<{name:string, description:string, systemInstruction:string, toolsModel: string, subagentEnabled: boolean}>({name:"", description:"", systemInstruction:"", toolsModel:"", subagentEnabled: false});
|
||||
|
||||
// Save state to VS Code whenever it changes
|
||||
useEffect(() => {
|
||||
|
|
@ -62,13 +55,12 @@ const App: React.FC<AppProps> = () => {
|
|||
currentChatModel,
|
||||
currentEmbeddingsModel,
|
||||
currentCompletionModel,
|
||||
currentAgentModel,
|
||||
currentAgent,
|
||||
currentEnv: currentEnv,
|
||||
currentState,
|
||||
view
|
||||
});
|
||||
}, [displayText, inputText, currentToolsModel, currentChatModel, currentEmbeddingsModel, currentCompletionModel, currentAgentModel, currentAgent, currentEnv, currentState, view]);
|
||||
}, [displayText, inputText, currentToolsModel, currentChatModel, currentEmbeddingsModel, currentCompletionModel, currentAgent, currentEnv, currentState, view]);
|
||||
|
||||
useEffect(() => {
|
||||
// Listen for messages from the extension
|
||||
|
|
@ -78,9 +70,6 @@ const App: React.FC<AppProps> = () => {
|
|||
case 'updateToolsModel':
|
||||
setCurrentToolsModel(message.model || noModelSelected);
|
||||
break;
|
||||
case 'updateTmpAgentModel':
|
||||
setCurrentAgentModel(message.model || "");
|
||||
break;
|
||||
case 'updateChatModel':
|
||||
setCurrentChatModel(message.model || noModelSelected);
|
||||
break;
|
||||
|
|
@ -102,17 +91,6 @@ const App: React.FC<AppProps> = () => {
|
|||
case 'updateContextFiles':
|
||||
setContextFiles(new Map(message.files || []));
|
||||
break;
|
||||
case 'updateContextImage':
|
||||
setContextImage(message.image || "");
|
||||
break;
|
||||
case 'updateAgentEdit':
|
||||
setAgentEditDetails({name: message.name, description: message.description, systemInstruction: message.systemInstruction, toolsModel: message.toolsModel,subagentEnabled: message.subagentEnabled});
|
||||
break;
|
||||
case 'loadAgent':
|
||||
setAgentEditDetails({name: message.name, description: message.description, systemInstruction: message.systemInstruction, toolsModel: message.toolsModel, subagentEnabled: message.subagentEnabled});
|
||||
setCurrentAgentModel(message.toolsModel);
|
||||
setAgentEditTools(new Map(message.tools || []));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
@ -146,11 +124,6 @@ const App: React.FC<AppProps> = () => {
|
|||
});
|
||||
};
|
||||
|
||||
const handleShowAgentEditor = () => {
|
||||
vscode.postMessage({
|
||||
command: 'showAgentEditor'
|
||||
});
|
||||
};
|
||||
|
||||
const handleSelectedModels = () => {
|
||||
vscode.postMessage({
|
||||
|
|
@ -189,23 +162,18 @@ const App: React.FC<AppProps> = () => {
|
|||
<button
|
||||
onClick={handleShowAgentView}
|
||||
className="header-btn secondary"
|
||||
title="Show Llama Agent View"
|
||||
title="Show Llama Agent"
|
||||
>
|
||||
{"💬"}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleShowAgentEditor}
|
||||
className="header-btn secondary"
|
||||
title="Show Edit Agent View"
|
||||
>
|
||||
{"✎"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{view === noViewSet && (
|
||||
<div>
|
||||
{/* Environment Selection Header for Agent View */}
|
||||
|
||||
|
||||
<AgentView
|
||||
displayText={displayText}
|
||||
setDisplayText={setDisplayText}
|
||||
|
|
@ -217,30 +185,10 @@ const App: React.FC<AppProps> = () => {
|
|||
setCurrentState={setCurrentState}
|
||||
contextFiles={contextFiles}
|
||||
setContextFiles={setContextFiles}
|
||||
imagePath={imagePath}
|
||||
setContextImage={setContextImage}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{view === viewAgentEditor && (
|
||||
<div>
|
||||
{/* Environment Selection Header for Agent View */}
|
||||
<AgentEditor
|
||||
inputText={inputText}
|
||||
setInputText={setInputText}
|
||||
currentAgentModel={currentAgentModel}
|
||||
currentAgent={currentAgent}
|
||||
currentState={currentState}
|
||||
setCurrentState={setCurrentState}
|
||||
contextFiles={agentEditTools}
|
||||
setContextFiles={setAgentEditTools}
|
||||
agentEditDetails={agentEditDetails}
|
||||
setAgentEditDetails = {setAgentEditDetails}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{view === viewAiRunner && (
|
||||
<div className="content">
|
||||
<AIRunnerView currentChatModel={currentChatModel} />
|
||||
|
|
|
|||
|
|
@ -10,10 +10,6 @@ interface AddEnvViewProps {
|
|||
completionsEnabled?: boolean;
|
||||
ragEnabled?: boolean;
|
||||
autoStartEnv?: boolean;
|
||||
healthCheckComplEnabled?: boolean;
|
||||
healthCheckChatEnabled?: boolean;
|
||||
healthCheckEmbsEnabled?: boolean,
|
||||
healthCheckToolsEnabled?: boolean
|
||||
}
|
||||
|
||||
const noModelSelected = 'No model selected';
|
||||
|
|
@ -27,19 +23,11 @@ const AddEnvView: React.FC<AddEnvViewProps> = ({
|
|||
currentAgent,
|
||||
completionsEnabled = false,
|
||||
ragEnabled = false,
|
||||
autoStartEnv = false,
|
||||
healthCheckComplEnabled = false,
|
||||
healthCheckChatEnabled = false,
|
||||
healthCheckEmbsEnabled = false,
|
||||
healthCheckToolsEnabled = false
|
||||
autoStartEnv = false
|
||||
}) => {
|
||||
const [isCompletionsEnabled, setIsCompletionsEnabled] = useState(completionsEnabled);
|
||||
const [isRagEnabled, setIsRagEnabled] = useState(ragEnabled);
|
||||
const [isAutoStartEnv, setIsAutoStartEnv] = useState(autoStartEnv);
|
||||
const [isHealthCheckComplEnabled, setIsHealthCheckComplEnabled] = useState(healthCheckComplEnabled);
|
||||
const [isHealthCheckChatEnabled, setIsHealthCheckChatEnabled] = useState(healthCheckChatEnabled);
|
||||
const [isHealthCheckEmbsEnabled, setIsHealthCheckEmbsEnabled] = useState(healthCheckEmbsEnabled);
|
||||
const [isHealthCheckToolsEnabled, setIsHealthCheckToolsEnabled] = useState(healthCheckToolsEnabled);
|
||||
|
||||
// Get the VS Code setting on component mount
|
||||
useEffect(() => {
|
||||
|
|
@ -62,30 +50,6 @@ const AddEnvView: React.FC<AddEnvViewProps> = ({
|
|||
key: 'env_start_last_used'
|
||||
});
|
||||
}, 1000);
|
||||
setTimeout(() => {
|
||||
vscode.postMessage({
|
||||
command: 'getVscodeSetting',
|
||||
key: 'health_check_compl_enabled'
|
||||
});
|
||||
}, 1000);
|
||||
setTimeout(() => {
|
||||
vscode.postMessage({
|
||||
command: 'getVscodeSetting',
|
||||
key: 'health_check_chat_enabled'
|
||||
});
|
||||
}, 1000);
|
||||
setTimeout(() => {
|
||||
vscode.postMessage({
|
||||
command: 'getVscodeSetting',
|
||||
key: 'health_check_embs_enabled'
|
||||
});
|
||||
}, 1000);
|
||||
setTimeout(() => {
|
||||
vscode.postMessage({
|
||||
command: 'getVscodeSetting',
|
||||
key: 'health_check_tools_enabled'
|
||||
});
|
||||
}, 1000);
|
||||
}, []);
|
||||
|
||||
// Listen for messages from the extension
|
||||
|
|
@ -93,29 +57,9 @@ const AddEnvView: React.FC<AddEnvViewProps> = ({
|
|||
const handleMessage = (event: MessageEvent) => {
|
||||
const message = event.data;
|
||||
if (message.command === 'vscodeSettingValue') {
|
||||
switch (message.key) {
|
||||
case 'health_check_compl_enabled':
|
||||
setIsHealthCheckComplEnabled(message.value);
|
||||
break;
|
||||
case 'health_check_chat_enabled':
|
||||
setIsHealthCheckChatEnabled(message.value);
|
||||
break;
|
||||
case 'health_check_embs_enabled':
|
||||
setIsHealthCheckEmbsEnabled(message.value);
|
||||
break;
|
||||
case 'health_check_tools_enabled':
|
||||
setIsHealthCheckToolsEnabled(message.value);
|
||||
break;
|
||||
case 'enabled':
|
||||
setIsCompletionsEnabled(message.value);
|
||||
break;
|
||||
case 'rag_enabled':
|
||||
setIsRagEnabled(message.value);
|
||||
break;
|
||||
case 'env_start_last_used':
|
||||
setIsAutoStartEnv(message.value);
|
||||
break;
|
||||
}
|
||||
if (message.key === 'enabled') setIsCompletionsEnabled(message.value);
|
||||
else if (message.key === 'rag_enabled') setIsRagEnabled(message.value);
|
||||
else if (message.key === 'env_start_last_used') setIsAutoStartEnv(message.value);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -152,44 +96,6 @@ const AddEnvView: React.FC<AddEnvViewProps> = ({
|
|||
command: 'deselectCompletionModel'
|
||||
});
|
||||
};
|
||||
|
||||
const handleMoreCompletionModel = () => {
|
||||
vscode.postMessage({
|
||||
command: 'moreCompletionModel'
|
||||
});
|
||||
};
|
||||
|
||||
const handleHealthCheck = (model: string) => {
|
||||
vscode.postMessage({
|
||||
command: 'checkModelHealth',
|
||||
model
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const handleMoreChatModel = () => {
|
||||
vscode.postMessage({
|
||||
command: 'moreChatModel'
|
||||
});
|
||||
};
|
||||
|
||||
const handleMoreEmbeddingsModel = () => {
|
||||
vscode.postMessage({
|
||||
command: 'moreEmbeddingsModel'
|
||||
});
|
||||
};
|
||||
|
||||
const handleMoreToolsModel = () => {
|
||||
vscode.postMessage({
|
||||
command: 'moreToolsModel'
|
||||
});
|
||||
};
|
||||
|
||||
const handleMoreAgent = () => {
|
||||
vscode.postMessage({
|
||||
command: 'moreAgent'
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeselectChatModel = () => {
|
||||
vscode.postMessage({
|
||||
|
|
@ -239,28 +145,12 @@ const AddEnvView: React.FC<AddEnvViewProps> = ({
|
|||
});
|
||||
};
|
||||
|
||||
const handleShowAgent = () => {
|
||||
const handleShowAgentModel = () => {
|
||||
vscode.postMessage({
|
||||
command: 'showAgentDetails'
|
||||
});
|
||||
};
|
||||
|
||||
const handleEditAgent = () => {
|
||||
vscode.postMessage({
|
||||
command: 'showAgentEditor'
|
||||
});
|
||||
|
||||
vscode.postMessage({
|
||||
command: 'editSelectedAgent',
|
||||
agent: currentAgent,
|
||||
});
|
||||
|
||||
vscode.postMessage({
|
||||
command: 'refreshEditedAgentTool',
|
||||
agent: currentAgent,
|
||||
});
|
||||
};
|
||||
|
||||
const handleSelectAgent = () => {
|
||||
vscode.postMessage({
|
||||
command: 'selectAgent'
|
||||
|
|
@ -299,7 +189,6 @@ const AddEnvView: React.FC<AddEnvViewProps> = ({
|
|||
|
||||
return (
|
||||
<div>
|
||||
<h1 style={{ margin: '0 0 20px 0', fontSize: '24px', fontWeight: 'bold' }}>Environment</h1>
|
||||
<div className="llm-buttons">
|
||||
<div className="single-button-frame">
|
||||
<div className="frame-label">Compl Model</div>
|
||||
|
|
@ -322,25 +211,6 @@ const AddEnvView: React.FC<AddEnvViewProps> = ({
|
|||
</button>
|
||||
)}
|
||||
<span className="model-text">{currentCompletionModel}</span>
|
||||
{currentCompletionModel != noModelSelected && isHealthCheckComplEnabled && (
|
||||
<button
|
||||
onClick={()=>handleHealthCheck("completion")}
|
||||
title={`Check Health of Completion Model`}
|
||||
className="modern-btn secondary"
|
||||
style={{ color: currentCompletionModel.includes("Error") ? "red" : "green" }}
|
||||
>
|
||||
{currentCompletionModel.includes("Error") ? "X": "V"}
|
||||
</button>
|
||||
)}
|
||||
{currentCompletionModel === noModelSelected && (
|
||||
<button
|
||||
onClick={handleMoreCompletionModel}
|
||||
title={`Add/Delete/View/Export/Import Completion Model`}
|
||||
className="modern-btn secondary"
|
||||
>
|
||||
More
|
||||
</button>
|
||||
)}
|
||||
{currentCompletionModel != noModelSelected && (
|
||||
<button
|
||||
onClick={handleShowCompletionModel}
|
||||
|
|
@ -374,25 +244,6 @@ const AddEnvView: React.FC<AddEnvViewProps> = ({
|
|||
</button>
|
||||
)}
|
||||
<span className="model-text">{currentChatModel}</span>
|
||||
{currentChatModel != noModelSelected && isHealthCheckChatEnabled && (
|
||||
<button
|
||||
onClick={()=>handleHealthCheck("chat")}
|
||||
title={`Check Health of Chat Model`}
|
||||
className="modern-btn secondary"
|
||||
style={{ color: currentChatModel.includes("Error") ? "red" : "green" }}
|
||||
>
|
||||
{currentChatModel.includes("Error") ? "X": "V"}
|
||||
</button>
|
||||
)}
|
||||
{currentChatModel === noModelSelected && (
|
||||
<button
|
||||
onClick={handleMoreChatModel}
|
||||
title={`Add/Delete/View/Export/Import Chat Model`}
|
||||
className="modern-btn secondary"
|
||||
>
|
||||
More
|
||||
</button>
|
||||
)}
|
||||
{currentChatModel != noModelSelected && (
|
||||
<button
|
||||
onClick={handleShowChatModel}
|
||||
|
|
@ -426,25 +277,6 @@ const AddEnvView: React.FC<AddEnvViewProps> = ({
|
|||
</button>
|
||||
)}
|
||||
<span className="model-text">{currentEmbeddingsModel}</span>
|
||||
{currentEmbeddingsModel != noModelSelected && isHealthCheckEmbsEnabled && (
|
||||
<button
|
||||
onClick={()=>handleHealthCheck("embeddings")}
|
||||
title={`Check Health of Embeddings Model`}
|
||||
className="modern-btn secondary"
|
||||
style={{ color: currentEmbeddingsModel.includes("Error") ? "red" : "green" }}
|
||||
>
|
||||
{currentEmbeddingsModel.includes("Error") ? "X": "V"}
|
||||
</button>
|
||||
)}
|
||||
{currentEmbeddingsModel === noModelSelected && (
|
||||
<button
|
||||
onClick={handleMoreEmbeddingsModel}
|
||||
title={`Add/Delete/View/Export/Import Embeddings Model`}
|
||||
className="modern-btn secondary"
|
||||
>
|
||||
More
|
||||
</button>
|
||||
)}
|
||||
{currentEmbeddingsModel != noModelSelected && (
|
||||
<button
|
||||
onClick={handleShowEmbsModel}
|
||||
|
|
@ -479,25 +311,6 @@ const AddEnvView: React.FC<AddEnvViewProps> = ({
|
|||
</button>
|
||||
)}
|
||||
<span className="model-text">{currentToolsModel}</span>
|
||||
{currentToolsModel != noModelSelected && isHealthCheckToolsEnabled && (
|
||||
<button
|
||||
onClick={()=>handleHealthCheck("tools")}
|
||||
title={`Check Health of Tools Model`}
|
||||
className="modern-btn secondary"
|
||||
style={{ color: currentToolsModel.includes("Error") ? "red" : "green" }}
|
||||
>
|
||||
{currentToolsModel.includes("Error") ? "X": "V"}
|
||||
</button>
|
||||
)}
|
||||
{currentToolsModel === noModelSelected && (
|
||||
<button
|
||||
onClick={handleMoreToolsModel}
|
||||
title={`Add/Delete/View/Export/Import Tools Model`}
|
||||
className="modern-btn secondary"
|
||||
>
|
||||
More
|
||||
</button>
|
||||
)}
|
||||
{currentToolsModel != noModelSelected && (
|
||||
<button
|
||||
onClick={handleShowToolsModel}
|
||||
|
|
@ -531,33 +344,15 @@ const AddEnvView: React.FC<AddEnvViewProps> = ({
|
|||
</button>
|
||||
)}
|
||||
<span className="model-text">{currentAgent}</span>
|
||||
{currentAgent === noAgentSelected && (
|
||||
<button
|
||||
onClick={handleMoreAgent}
|
||||
title={`Add/Delete/View/Export/Import Agentl`}
|
||||
className="modern-btn secondary"
|
||||
>
|
||||
More
|
||||
</button>
|
||||
)}
|
||||
{currentAgent != noAgentSelected && (
|
||||
<button
|
||||
onClick={handleShowAgent}
|
||||
onClick={handleShowAgentModel}
|
||||
title={`Show Agent Details`}
|
||||
className="modern-btn secondary"
|
||||
>
|
||||
...
|
||||
</button>
|
||||
)}
|
||||
{currentAgent != noAgentSelected && (
|
||||
<button
|
||||
onClick={handleEditAgent}
|
||||
title={`Show Agent Details`}
|
||||
className="modern-btn secondary"
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="llm-buttons">
|
||||
|
|
|
|||
|
|
@ -1,519 +0,0 @@
|
|||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { vscode } from '../types/vscode';
|
||||
|
||||
interface AgentEditorProps {
|
||||
inputText: string;
|
||||
setInputText: (text: string) => void;
|
||||
currentAgentModel: string;
|
||||
currentAgent: string;
|
||||
currentState: string;
|
||||
setCurrentState: (state: string) => void;
|
||||
contextFiles: Map<string, string>;
|
||||
setContextFiles: (files: Map<string, string>) => void;
|
||||
agentEditDetails: {name: string, description: string, systemInstruction: string, toolsModel: string, subagentEnabled: boolean}
|
||||
setAgentEditDetails:(agentDetails: {name: string, description: string, systemInstruction: string, toolsModel: string, subagentEnabled: boolean}) => void
|
||||
}
|
||||
|
||||
const AgentEditor: React.FC<AgentEditorProps> = ({
|
||||
inputText,
|
||||
setInputText,
|
||||
currentAgentModel,
|
||||
currentAgent,
|
||||
currentState,
|
||||
setCurrentState,
|
||||
contextFiles: agentTools,
|
||||
setContextFiles: setTools,
|
||||
agentEditDetails,
|
||||
setAgentEditDetails
|
||||
}) => {
|
||||
const [showFileSelector, setShowFileSelector] = useState<boolean>(false);
|
||||
const [fileList, setFileList] = useState<string[]>([]);
|
||||
const [fileFilter, setFileFilter] = useState<string>('');
|
||||
const [selectedIndex, setSelectedIndex] = useState<number>(0);
|
||||
|
||||
// Create refs
|
||||
const elemSystemPromptRef = useRef<HTMLTextAreaElement>(null);
|
||||
const elemDescriptionRef = useRef<HTMLTextAreaElement>(null);
|
||||
const elemNameRef = useRef<HTMLTextAreaElement>(null);
|
||||
const fileListRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Auto-focus the textarea when the component mounts
|
||||
useEffect(() => {
|
||||
if (elemSystemPromptRef.current) {
|
||||
elemSystemPromptRef.current.focus();
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Filter files based on user input
|
||||
const filteredFiles = fileList.filter(file =>
|
||||
file.toLowerCase().includes(fileFilter.toLowerCase())
|
||||
);
|
||||
|
||||
// Reset selected index when file list or filter changes
|
||||
useEffect(() => {
|
||||
setSelectedIndex(0);
|
||||
}, [fileList, fileFilter]);
|
||||
|
||||
// Auto-scroll to keep selected item visible
|
||||
useEffect(() => {
|
||||
if (showFileSelector && fileListRef.current && filteredFiles.length > 0) {
|
||||
const fileItems = fileListRef.current.querySelectorAll('.file-item');
|
||||
if (fileItems[selectedIndex]) {
|
||||
fileItems[selectedIndex].scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'nearest'
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [selectedIndex, showFileSelector, filteredFiles.length]);
|
||||
|
||||
useEffect(() => {
|
||||
// Listen for messages from the extension
|
||||
const handleMessage = (event: MessageEvent) => {
|
||||
const message = event.data;
|
||||
console.log('Received message from extension:', message);
|
||||
switch (message.command) {
|
||||
case 'focusTextarea':
|
||||
// Focus the textarea when requested by the extension
|
||||
if (elemSystemPromptRef.current) {
|
||||
elemSystemPromptRef.current.focus();
|
||||
}
|
||||
break;
|
||||
case 'updateCurrentState':
|
||||
setCurrentState(message.text || '');
|
||||
break;
|
||||
case 'updateFileList':
|
||||
setFileList(message.files || []);
|
||||
setShowFileSelector(true);
|
||||
break;
|
||||
case 'updateAgentTools':
|
||||
setTools(new Map(message.files || []));
|
||||
break;
|
||||
case 'loadAgent':
|
||||
setAgentEditDetails({name: message.name, description: message.description, systemInstruction: message.systemInstruction, toolsModel: message.toolsModel, subagentEnabled: message.subagentEnabled});
|
||||
setTools(new Map(message.tools || []));
|
||||
currentAgentModel = message.toolsModel;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', handleMessage);
|
||||
return () => window.removeEventListener('message', handleMessage);
|
||||
}, [setCurrentState, setTools]);
|
||||
|
||||
// Function to focus the textarea (can be called from extension)
|
||||
const focusTextarea = () => {
|
||||
if (elemSystemPromptRef.current) {
|
||||
elemSystemPromptRef.current.focus();
|
||||
}
|
||||
};
|
||||
|
||||
// Expose the focus function to the extension
|
||||
useEffect(() => {
|
||||
// @ts-ignore - Adding to window for extension access
|
||||
window.focusTextarea = focusTextarea;
|
||||
}, []);
|
||||
|
||||
const handlеSaveAgent = () => {
|
||||
vscode.postMessage({
|
||||
command: 'saveEditAgent',
|
||||
name: agentEditDetails.name,
|
||||
subagentEnabled: agentEditDetails.subagentEnabled,
|
||||
description: agentEditDetails.description,
|
||||
systemInstruction: agentEditDetails.systemInstruction,
|
||||
toolsModel: currentAgentModel,
|
||||
tools: Array.from(agentTools.keys())
|
||||
});
|
||||
};
|
||||
|
||||
const handleSelectTools = () => {
|
||||
vscode.postMessage({
|
||||
command: "configureEditTools",
|
||||
tools: Array.from(agentTools.keys())
|
||||
});
|
||||
}
|
||||
|
||||
const handleDeselectToolsModel = () => {
|
||||
vscode.postMessage({
|
||||
command: 'deselectAgentModel'
|
||||
});
|
||||
};
|
||||
|
||||
const handleSelectToolsModel = () => {
|
||||
vscode.postMessage({
|
||||
command: 'selectAgentModel'
|
||||
});
|
||||
};
|
||||
|
||||
const handleMoreToolsModel = () => {
|
||||
vscode.postMessage({
|
||||
command: 'moreToolsModel'
|
||||
});
|
||||
};
|
||||
|
||||
const handleShowToolsModel = () => {
|
||||
vscode.postMessage({
|
||||
command: 'showToolsModel'
|
||||
});
|
||||
};
|
||||
|
||||
const handleSelectEditAgent = () => {
|
||||
vscode.postMessage({
|
||||
command: 'selectEditAgent'
|
||||
});
|
||||
}
|
||||
|
||||
const handleNewAgent = () => {
|
||||
vscode.postMessage({
|
||||
command: 'addEditAgent',
|
||||
name: "",
|
||||
description: "",
|
||||
systemInstruction: [],
|
||||
toolsModel: "",
|
||||
tools: []
|
||||
});
|
||||
}
|
||||
|
||||
const handleCopyAgent = () => {
|
||||
vscode.postMessage({
|
||||
command: 'copyAsNewAgent'
|
||||
});
|
||||
}
|
||||
|
||||
const handleDeleteAgent = () => {
|
||||
vscode.postMessage({
|
||||
command: 'deleteAgent'
|
||||
});
|
||||
}
|
||||
|
||||
const handleFileSelect = (fileLongName: string) => {
|
||||
// Send the selected file to the extension
|
||||
setShowFileSelector(false);
|
||||
setFileFilter('');
|
||||
vscode.postMessage({
|
||||
command: 'addEditedAgentTool',
|
||||
fileLongName: fileLongName
|
||||
});
|
||||
|
||||
if (elemSystemPromptRef.current) {
|
||||
elemSystemPromptRef.current.focus();
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancelFileSelect = () => {
|
||||
setShowFileSelector(false);
|
||||
setFileFilter('');
|
||||
if (elemSystemPromptRef.current) {
|
||||
elemSystemPromptRef.current.focus();
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveTool = (fileLongName: string) => {
|
||||
vscode.postMessage({
|
||||
command: 'removeEditedAgentTool',
|
||||
fileLongName: fileLongName
|
||||
});
|
||||
};
|
||||
|
||||
const handleOpenContextFile = (fileLongName: string) => {
|
||||
vscode.postMessage({
|
||||
command: 'openContextFile',
|
||||
fileLongName: fileLongName
|
||||
});
|
||||
};
|
||||
|
||||
// Handle keyboard navigation in file selector
|
||||
const handleFileSelectorKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (!showFileSelector || filteredFiles.length === 0) return;
|
||||
|
||||
switch (e.key) {
|
||||
case 'ArrowDown':
|
||||
e.preventDefault();
|
||||
setSelectedIndex(prev =>
|
||||
prev < filteredFiles.length - 1 ? prev + 1 : prev
|
||||
);
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
e.preventDefault();
|
||||
setSelectedIndex(prev => prev > 0 ? prev - 1 : 0);
|
||||
break;
|
||||
case 'Enter':
|
||||
e.preventDefault();
|
||||
if (filteredFiles.length > 0) {
|
||||
handleFileSelect(filteredFiles[selectedIndex]);
|
||||
}
|
||||
break;
|
||||
case 'Escape':
|
||||
e.preventDefault();
|
||||
handleCancelFileSelect();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// Handle keyboard events in the search input
|
||||
const handleSearchKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (!showFileSelector || filteredFiles.length === 0) return;
|
||||
|
||||
switch (e.key) {
|
||||
case 'ArrowDown':
|
||||
e.preventDefault();
|
||||
// Focus the file list and set first item as selected
|
||||
if (fileListRef.current) {
|
||||
fileListRef.current.focus();
|
||||
setSelectedIndex(0);
|
||||
}
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
e.preventDefault();
|
||||
// Focus the file list and set last item as selected
|
||||
if (fileListRef.current) {
|
||||
fileListRef.current.focus();
|
||||
setSelectedIndex(filteredFiles.length - 1);
|
||||
}
|
||||
break;
|
||||
case 'Enter':
|
||||
e.preventDefault();
|
||||
// If there are files, focus the list and select the first one
|
||||
if (filteredFiles.length > 0) {
|
||||
if (fileListRef.current) {
|
||||
fileListRef.current.focus();
|
||||
setSelectedIndex(0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'Escape':
|
||||
e.preventDefault();
|
||||
handleCancelFileSelect();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}>
|
||||
{/* Main Content */}
|
||||
<div className="content" style={{ flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0 }}>
|
||||
<div className="input-section" style={{ flexShrink: 0 }}>
|
||||
<div className="input-container">
|
||||
{/* Page Title */}
|
||||
<h1 style={{ margin: '0 0 20px 0', fontSize: '24px', fontWeight: 'bold' }}>Agent Editor</h1>
|
||||
<div style={{ display: 'flex', gap: '10px', marginBottom: '20px' }}>
|
||||
<button
|
||||
onClick={handleSelectEditAgent}
|
||||
title={`Select Agent to Edit`}
|
||||
className="modern-btn secondary"
|
||||
>
|
||||
Select
|
||||
</button>
|
||||
<button
|
||||
onClick={handleNewAgent}
|
||||
title={`New Agent`}
|
||||
className="modern-btn secondary"
|
||||
>
|
||||
New
|
||||
</button>
|
||||
<button
|
||||
onClick={handleCopyAgent}
|
||||
title={`Copy Existing Agent as New`}
|
||||
className="modern-btn secondary"
|
||||
>
|
||||
Copy as New
|
||||
</button>
|
||||
<button
|
||||
onClick={handleDeleteAgent}
|
||||
title={`Select and Delete Existing Agent`}
|
||||
className="modern-btn secondary"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
<span style={{ display: 'block', marginTop: '20px', marginBottom: '10px', fontWeight: 'bold' }}>{'Tools'}</span>
|
||||
{/* Tools */}
|
||||
{agentTools.size > 0 && (
|
||||
<div className="context-chips">
|
||||
{Array.from(agentTools.entries()).map(([toolName, toolDescription]) => (
|
||||
<div key={toolName} className="context-chip">
|
||||
<span
|
||||
className="file-name clickable"
|
||||
onClick={() => handleOpenContextFile(toolName)}
|
||||
>
|
||||
{toolName}
|
||||
</span>
|
||||
<button
|
||||
className="remove-btn"
|
||||
onClick={() => handleRemoveTool(toolName)}
|
||||
title={`Remove ${toolName} from context`}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
<span style={{ display: 'block', marginTop: '20px', marginBottom: '10px', fontWeight: 'bold' }}>{'Name (agent identifier)'}</span>
|
||||
{/* Modern Textarea */}
|
||||
<textarea
|
||||
ref={elemNameRef}
|
||||
value={agentEditDetails.name}
|
||||
onChange={(e) => setAgentEditDetails({name: e.target.value, description: agentEditDetails.description, systemInstruction: agentEditDetails.systemInstruction, toolsModel: agentEditDetails.toolsModel,subagentEnabled: agentEditDetails.subagentEnabled})}
|
||||
placeholder="Enter agent name."
|
||||
className="modern-textarea"
|
||||
rows={1}
|
||||
style={{ height: 'auto', minHeight: '1.5em', resize: 'none' }}
|
||||
/>
|
||||
|
||||
<span style={{ display: 'block', marginTop: '20px', marginBottom: '10px', fontWeight: 'bold' }}>{'Description'}</span>
|
||||
{/* Modern Textarea */}
|
||||
<textarea
|
||||
ref={elemDescriptionRef}
|
||||
value={agentEditDetails.description}
|
||||
onChange={(e) => setAgentEditDetails({name: agentEditDetails.name, description: e.target.value, systemInstruction: agentEditDetails.systemInstruction, toolsModel: agentEditDetails.toolsModel, subagentEnabled: agentEditDetails.subagentEnabled})}
|
||||
placeholder="Enter agent description."
|
||||
className="modern-textarea"
|
||||
rows={2}
|
||||
style={{ height: 'auto', minHeight: '3em', resize: 'none' }}
|
||||
/>
|
||||
|
||||
<span style={{ display: 'block', marginTop: '20px', marginBottom: '10px', fontWeight: 'bold' }}>{'System Instruction'}</span>
|
||||
{/* Modern Textarea */}
|
||||
<textarea
|
||||
ref={elemSystemPromptRef}
|
||||
value={agentEditDetails.systemInstruction}
|
||||
onChange={(e) => setAgentEditDetails({name: agentEditDetails.name, description: agentEditDetails.description, systemInstruction: e.target.value, toolsModel: agentEditDetails.toolsModel, subagentEnabled: agentEditDetails.subagentEnabled})}
|
||||
placeholder="Enter system instructions for the agent."
|
||||
className="modern-textarea"
|
||||
rows={10}
|
||||
style={{ height: 'auto', minHeight: '15em', resize: 'vertical' }}
|
||||
/>
|
||||
<div style={{ marginBottom: '20px', marginTop: '10px', fontWeight: 'bold' }}>
|
||||
<label className="checkbox-label" title="If enabled - the agent could be used as a subagent.">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={agentEditDetails.subagentEnabled}
|
||||
onChange={(e) => setAgentEditDetails({name: agentEditDetails.name, description: e.target.value, systemInstruction: agentEditDetails.systemInstruction, toolsModel: agentEditDetails.toolsModel, subagentEnabled: e.target.checked})}
|
||||
/>
|
||||
<span style={{ marginLeft: '8px' }}>Available as Subagent</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="single-button-frame">
|
||||
<div className="frame-label">Agent Model (Optional)</div>
|
||||
{currentAgentModel == "" && (
|
||||
<button
|
||||
onClick={handleSelectToolsModel}
|
||||
title={`Add Default Agent (Tools) Model`}
|
||||
className="modern-btn secondary"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
)}
|
||||
{
|
||||
currentAgentModel && (
|
||||
<button
|
||||
onClick={handleDeselectToolsModel}
|
||||
title={`Remove Agent Model`}
|
||||
className="modern-btn secondary"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
)
|
||||
}
|
||||
<span className="model-text">{currentAgentModel}</span>
|
||||
{
|
||||
currentAgentModel === "" && (
|
||||
<button
|
||||
onClick={handleMoreToolsModel}
|
||||
title={`Add/Delete/View/Export/Import Tools Model`}
|
||||
className="modern-btn secondary"
|
||||
>
|
||||
More
|
||||
</button>
|
||||
)
|
||||
}
|
||||
{
|
||||
currentAgentModel && (
|
||||
<button
|
||||
onClick={handleShowToolsModel}
|
||||
title={`Show Tools Model Details`}
|
||||
className="modern-btn secondary"
|
||||
>
|
||||
...
|
||||
</button>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
{/* Input Actions */}
|
||||
<div className="input-actions">
|
||||
<div className="input-buttons">
|
||||
<button
|
||||
onClick={handleSelectTools}
|
||||
className="modern-btn secondary"
|
||||
title="Add/remove tools to the agent"
|
||||
>
|
||||
Add/Remove Tools
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
onClick={handlеSaveAgent}
|
||||
className={`modern-btn ${inputText.trim() === '' ? 'secondary' : ''}`}
|
||||
title={"Saves the agent changes or creates a new agent if the name does not exit."}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* File Selection Dialog */}
|
||||
{showFileSelector && (
|
||||
<div className="file-selector-overlay">
|
||||
<div className="file-selector-dialog">
|
||||
<div className="file-selector-header">
|
||||
<h3>Select an item to add to context</h3>
|
||||
<button onClick={handleCancelFileSelect} className="close-btn">×</button>
|
||||
</div>
|
||||
<div className="file-selector-search">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Filter ..."
|
||||
value={fileFilter}
|
||||
onChange={(e) => setFileFilter(e.target.value)}
|
||||
onKeyDown={handleSearchKeyDown}
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
ref={fileListRef}
|
||||
className="file-selector-list"
|
||||
onKeyDown={handleFileSelectorKeyDown}
|
||||
tabIndex={0}
|
||||
>
|
||||
{filteredFiles.length > 0 ? (
|
||||
filteredFiles.map((file, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`file-item ${index === selectedIndex ? 'selected' : ''}`}
|
||||
onClick={() => handleFileSelect(file)}
|
||||
>
|
||||
{file}
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="no-files">
|
||||
{fileFilter ? 'No files match your filter' : 'No files available'}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AgentEditor;
|
||||
|
|
@ -14,8 +14,6 @@ interface AgentViewProps {
|
|||
setCurrentState: (state: string) => void;
|
||||
contextFiles: Map<string, string>;
|
||||
setContextFiles: (files: Map<string, string>) => void;
|
||||
imagePath: string;
|
||||
setContextImage: (imgPath: string) => void;
|
||||
}
|
||||
|
||||
const AgentView: React.FC<AgentViewProps> = ({
|
||||
|
|
@ -28,9 +26,7 @@ const AgentView: React.FC<AgentViewProps> = ({
|
|||
currentState,
|
||||
setCurrentState,
|
||||
contextFiles,
|
||||
setContextFiles,
|
||||
imagePath,
|
||||
setContextImage
|
||||
setContextFiles
|
||||
}) => {
|
||||
const [showFileSelector, setShowFileSelector] = useState<boolean>(false);
|
||||
const [fileList, setFileList] = useState<string[]>([]);
|
||||
|
|
@ -113,9 +109,6 @@ const AgentView: React.FC<AgentViewProps> = ({
|
|||
case 'updateContextFiles':
|
||||
setContextFiles(new Map(message.files || []));
|
||||
break;
|
||||
case 'updateContextImage':
|
||||
setContextImage(message.image || "");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
@ -123,7 +116,7 @@ const AgentView: React.FC<AgentViewProps> = ({
|
|||
|
||||
window.addEventListener('message', handleMessage);
|
||||
return () => window.removeEventListener('message', handleMessage);
|
||||
}, [setDisplayText, setCurrentState, setContextFiles, setContextImage]);
|
||||
}, [setDisplayText, setCurrentState, setContextFiles]);
|
||||
|
||||
// Function to focus the textarea (can be called from extension)
|
||||
const focusTextarea = () => {
|
||||
|
|
@ -154,12 +147,6 @@ const AgentView: React.FC<AgentViewProps> = ({
|
|||
handleAddSource('getFileList');
|
||||
}
|
||||
|
||||
const handleAddImage = () => {
|
||||
vscode.postMessage({
|
||||
command: 'selectImageFile'
|
||||
});
|
||||
}
|
||||
|
||||
const handleSelectToolsModel = () => {
|
||||
vscode.postMessage({
|
||||
command: 'selectModelWithTools'
|
||||
|
|
@ -221,10 +208,6 @@ const AgentView: React.FC<AgentViewProps> = ({
|
|||
command: 'addContextProjectFile',
|
||||
fileLongName: fileLongName
|
||||
});
|
||||
// vscode.postMessage({
|
||||
// command: 'addContextProjectImage',
|
||||
// image: "/home/igardev/Downloads/sofia.jpeg"
|
||||
// });
|
||||
} else if (inputText.endsWith('/')){
|
||||
vscode.postMessage({
|
||||
command: 'sendAgentCommand',
|
||||
|
|
@ -233,11 +216,6 @@ const AgentView: React.FC<AgentViewProps> = ({
|
|||
});
|
||||
setInputText('');
|
||||
setCurrentState('AI is working...');
|
||||
} else {
|
||||
vscode.postMessage({
|
||||
command: 'addContextProjectFile',
|
||||
fileLongName: fileLongName
|
||||
});
|
||||
}
|
||||
|
||||
if (textareaRef.current) {
|
||||
|
|
@ -260,14 +238,6 @@ const AgentView: React.FC<AgentViewProps> = ({
|
|||
});
|
||||
};
|
||||
|
||||
const handleRemoveContextImage = (imagePath: string) => {
|
||||
vscode.postMessage({
|
||||
command: 'removeContextProjectImage',
|
||||
image: imagePath
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const handleOpenContextFile = (fileLongName: string) => {
|
||||
vscode.postMessage({
|
||||
command: 'openContextFile',
|
||||
|
|
@ -346,7 +316,7 @@ const AgentView: React.FC<AgentViewProps> = ({
|
|||
{/* Modern Header */}
|
||||
<div className="header">
|
||||
<div className="header-content">
|
||||
{!currentToolsModel.includes('No model selected...') && (
|
||||
{!currentToolsModel.includes('No model selected') && (
|
||||
<div className="header-left">
|
||||
<button
|
||||
onClick={handleClearText}
|
||||
|
|
@ -390,7 +360,7 @@ const AgentView: React.FC<AgentViewProps> = ({
|
|||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
{!currentToolsModel.includes('No model selected...') && (
|
||||
{!currentToolsModel.includes('No model selected') && (
|
||||
<div className="content" style={{ flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0 }}>
|
||||
{/* Chat Display Area */}
|
||||
{/* Markdown Display Area */}
|
||||
|
|
@ -428,25 +398,12 @@ const AgentView: React.FC<AgentViewProps> = ({
|
|||
</div>
|
||||
)}
|
||||
|
||||
{imagePath && (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '12px' }}>
|
||||
<span className="model-text">{"image: " + imagePath}</span>
|
||||
<button
|
||||
className="modern-btn secondary"
|
||||
onClick={() => handleRemoveContextImage(imagePath)}
|
||||
title="Remove image from context"
|
||||
style={{ padding: '4px 8px', fontSize: '12px', minWidth: 'auto' }}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{/* Modern Textarea */}
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
value={inputText}
|
||||
onChange={(e) => setInputText(e.target.value)}
|
||||
placeholder="Ask me anything about your code... Press @ to select a file, / for a command."
|
||||
placeholder="Ask me anything about your code... Press @ to select a file / for a command."
|
||||
className="modern-textarea"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
|
|
@ -491,14 +448,6 @@ const AgentView: React.FC<AgentViewProps> = ({
|
|||
>
|
||||
@
|
||||
</button>
|
||||
<button
|
||||
onClick={handleAddImage}
|
||||
className="modern-btn secondary"
|
||||
title="Add/replace image to context (.jpg, .png, .webp)"
|
||||
style={{ filter: 'grayscale(100%)' }}
|
||||
>
|
||||
🖼️
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
export { default as AgentView } from './AgentView';
|
||||
export { default as AIRunnerView } from './AIRunnerView';
|
||||
export { default as AddEnvView } from './AddEnvView';
|
||||
export { default as AgentEditor } from './AgentEditor';
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue