vim.keymap.set({'n', 'v'}, 'mct', 'CodeCompanionChat Toggle', { noremap = true, silent = true, desc = "CC Toggle" }) vim.keymap.set({'n', 'v'}, 'mca', 'CodeCompanionActions', { noremap = true, silent = true, desc = "CC Action" }) vim.keymap.set({'n', 'v'}, 'mce', 'CodeCompanion Explain', { desc = "CC Explain" }) vim.keymap.set({'n'}, 'mcf', 'CodeCompanion Fix code', { desc = "CC fix" }) vim.keymap.set({'v'}, 'mcc', 'CodeCompanionChat Add', { noremap = true, silent = true, desc = "CC Add" }) vim.keymap.set({'v'}, 'mcr', 'CodeCompanion Code Review', { desc = 'Code Review' }) vim.keymap.set({'v'}, 'mco', 'CodeCompanion Optimize Performance', { desc = 'Optimize Code' }) vim.keymap.set({'v'}, 'mcg', 'CodeCompanion Generate Tests', { desc = 'Generate Tests' }) vim.keymap.set({'v'}, 'mcd', 'CodeCompanion Debug Analysis', { desc = 'Debug Analysis' }) -- TODO: run this command -- build = "uv tool upgrade vectorcode", -- This helps keeping the CLI up-to-date vim.api.nvim_create_autocmd('PackChanged', { callback = function() vim.fn.jobstart({ "uv", "tool", "upgrade", "vectorcode" }, { on_exit = function(_, exit_code, _) if exit_code == 0 then vim.notify("vectorcode upgraded", vim.log.levels.INFO, { title = "CodeCompanion" }) else vim.notify("vectorcode upgrade failed (exit " .. exit_code .. ")", vim.log.levels.WARN, { title = "CodeCompanion" }) end end, }) end }) local function get_abacus_key() local ok, res = pcall(vim.fn.systemlist, "pass show abacus.ai-api") if not ok or not res or vim.tbl_isempty(res) then return "" end -- systemlist returns lines; join and trim whitespace local key = table.concat(res, "\n") key = key:gsub("^%s+", ""):gsub("%s+$", "") return key end local function copilot_supports_sampling(self) local model = self.schema.model.default if type(model) == "function" then model = model(self) end model = type(model) == "string" and model:lower() or "" return not model:find("gpt%-5", 1, false) and not model:find("codex", 1, false) end local ok, codecompanion = pcall(require, "codecompanion") if not ok or not codecompanion then vim.notify("codecompanion not available", vim.log.levels.ERROR) return end codecompanion.setup({ adapters = { http = { opts = { show_presets = true, show_model_choices = true, }, -- openai = function() -- return require("codecompanion.adapters").extend("openai", { -- env = { api_key = "cmd:pass show api.openai.com" } -- }) -- end, openrouter = function() return require("codecompanion.adapters").extend("openai_compatible", { env = { url = "https://openrouter.ai/api", api_key = "OPENROUTER_API_KEY", chat_url = "/v1/chat/completions", }, schema = { model = { default = "anthropic/claude-3.7-sonnet", }, }, }) end, abacus = function() return require("codecompanion.adapters").extend("openai_compatible", { name = "abacus", formatted_name = "Abacus.AI", env = { url = "https://routellm.abacus.ai/v1", -- IMPORTANT: include /v1 in the base url api_key = get_abacus_key(), chat_url = "/chat/completions" }, headers = { ["Content-Type"] = "application/json", ["Authorization"] = "Bearer ${api_key}", }, schema = { model = { order = 1, mapping = "parameters", type = "enum", desc = "The model to use for chat completions.", default = "route-llm", choices = { -- Routing Model ["route-llm"] = { opts = { desc = "Intelligent routing to best model" } }, -- OpenAI Models ["gpt-5.2"] = { opts = { has_vision = true } }, ["gpt-5.1"] = { opts = { has_vision = true } }, ["gpt-5.1-chat-latest"] = { opts = { has_vision = true } }, ["gpt-5"] = { opts = { has_vision = true } }, ["gpt-5-mini"] = { opts = { has_vision = true } }, ["gpt-5-nano"] = { opts = { has_vision = true } }, ["gpt-4o"] = { opts = { has_vision = true } }, ["gpt-4o-mini"] = { opts = { has_vision = true } }, ["o4-mini"] = { opts = { can_reason = true } }, ["o3"] = { opts = { can_reason = true } }, ["o3-pro"] = { opts = { can_reason = true } }, -- Anthropic Models ["claude-4-5-sonnet"] = { opts = { has_vision = true } }, ["claude-4-5-haiku"] = { opts = { has_vision = true } }, ["claude-4-5-opus"] = { opts = { has_vision = true } }, ["claude-3-opus"] = { opts = { has_vision = true } }, -- Google Models ["gemini-3-pro"] = { opts = { has_vision = true } }, ["gemini-3-flash"] = { opts = { has_vision = true } }, ["gemini-2.5-pro"] = { opts = { has_vision = true } }, ["gemini-2.5-flash"] = { opts = { has_vision = true } }, -- xAI Models ["grok-4-1-fast"] = {}, ["grok-4"] = {}, ["grok-code-fast-1"] = {}, -- Meta Models ["llama-4-Maverick-17B"] = {}, ["llama-3.1-405B"] = {}, ["llama-3.1-70B"] = {}, -- DeepSeek Models ["deepseek-v3.2"] = {}, ["deepseek-v3.1-Terminus"] = {}, ["deepseek-R1"] = { opts = { can_reason = true } }, -- Qwen Models ["qwen-3-Max"] = {}, ["qwen3-coder-480b-a35b-instruct"] = {}, ["qwen-3-32B"] = {}, ["qwq-32B"] = { opts = { can_reason = true } }, }, }, temperature = { order = 2, mapping = "parameters", type = "number", optional = true, default = 1.0, desc = "Controls randomness of output (0-2).", validate = function(n) return n >= 0 and n <= 2, "Must be between 0 and 2" end, }, max_tokens = { order = 3, mapping = "parameters", type = "integer", optional = true, default = nil, desc = "Maximum tokens to generate.", validate = function(n) return n > 0, "Must be greater than 0" end, }, top_p = { order = 4, mapping = "parameters", type = "number", optional = true, default = 1.0, desc = "Nucleus sampling parameter (0-1).", validate = function(n) return n >= 0 and n <= 1, "Must be between 0 and 1" end, }, }, }) end, ollama = function() local ollama_url = os.getenv("OLLAMA_URL") or "http://192.168.33.74:11434" return require("codecompanion.adapters").extend("ollama", { env = { url = ollama_url, }, schema = { model = { default = "qwen3.5", }, }, }) end, copilot = function() return require("codecompanion.adapters").extend("copilot", { schema = { model = { default = "gpt-5-mini", -- Consistent model choice }, -- CodeCompanion only skips schema fields when `enabled` is a -- function that returns false. temperature = { enabled = copilot_supports_sampling, }, top_p = { enabled = copilot_supports_sampling, }, n = { enabled = copilot_supports_sampling, }, max_tokens = { default = 16284, } } }) end, }, acp = { opts = { show_presets = true, }, copilot_cli = function() return require("codecompanion.adapters").extend("copilot_cli", {}) end, codex = function() return require("codecompanion.adapters").extend("codex", { -- commands = { -- default = { "codex-acp" }, -- }, -- schema = { -- model = { -- default = "github-copilot/gpt-5.2-codex", -- }, -- }, defaults = { timeout = 20000, auth_method = "chatgpt", -- "openai-api-key"|"codex-api-key"|"chatgpt" }, }) end, -- opencode = function() -- return require("codecompanion.adapters").extend("codex", { -- commands = { -- default = { "opencode", "acp" }, -- }, -- schema = { -- model = { -- default = "github-copilot/gpt-5.2-codex", -- }, -- }, -- defaults = { -- timeout = 20000, -- auth_method = "chatgpt", -- "openai-api-key"|"codex-api-key"|"chatgpt" -- }, -- }) -- end, }, }, rules = { opts = { chat = { enabled = true, autoload = { "default" } }, }, }, opts = { send_code = true, log_level = "WARN", -- Change to DEBUG for troubleshooting language = "English", -- NEW: Performance improvements job_start_delay = 500, -- Reduced from default 1500ms submit_delay = 1000, -- Reduced from default 2000ms }, extensions = { spinner = {}, -- ddgr_search = { -- opts = { -- require_approval_before = false, -- }, -- }, agentskills = { opts = { paths = { { vim.fn.expand("~/.agents/skills"), recursive = true }, }, }, }, -- vectorcode = { lazy = true }, history = { lazy = true, enabled = true, opts = { keymap = "ghf", save_chat_keymap = "ghs", picker = "snacks", dir_to_save = vim.env.HOME .. "/Documents/ai-history", title_generation_opts = { adapter = "copilot", model = "gpt-5-mini", }, summary = { create_summary_keymap = "ghc", browse_summaries_keymap = "ghb", }, }, }, -- mcphub = { -- callback = "mcphub.extensions.codecompanion", -- opts = { -- make_vars = true, -- make_slash_commands = true, -- show_result_in_chat = true, -- }, -- }, }, interactions = { chat = { variables = { project = { callback = function() -- Get project files structure local files = vim.fn.systemlist("fd . -type f") return table.concat(files, "\n") end, description = "Share project structure" } }, tools = { opts = { auto_submit_errors = false, -- Send any errors to the LLM automatically? auto_submit_success = false, -- Send any successful output to the LLM automatically? system_prompt = { enabled = true, replace_main_system_prompt = false, }, default_tools = { "ask_questions", "file_search", "read_file", "cmd_runner", "fetch", "grep_search", "memory", "agent_skills", -- "ddgr_search", }, }, -- ["memory"] = { -- opts = { -- whitelist = { -- { path = vim.fn.expand("~/.local/share/dotfiles"), as = "/dotfiles" }, -- { path = vim.fn.expand("~/.local/share/dotfiles/memories/profile.md"), as = "/personal" }, -- }, -- }, -- }, ["fetch_enhanced"] = { description = "Fetch a data from the web", callback = function(url, options) options = options or {} local max_length = options.max_length or 200000 -- 200KB default local timeout = options.timeout or 60 local res = vim.system({ "curl", "-L", "-s", "--max-time", tostring(timeout), "--user-agent", "CodeCompanion-Neovim", url }, { timeout = (timeout + 5) * 1000 }):wait() if not res or res.code ~= 0 then return nil, "Failed to fetch URL: " .. tostring(res and res.code or "unknown") end local result = res.output or "" if #result > max_length then result = result:sub(1, max_length) .. "\n[... truncated at " .. max_length .. " characters ...]" end return result end, opts = { requires_approval = false, } }, ["fetch_unlimited"] = { description = "Fetch a data from the web", callback = function(url, options) options = options or {} -- Use a local proxy service or direct system calls local temp_file = vim.fn.tempname() local cmd = string.format( 'wget --timeout=60 --tries=3 --output-document="%s" "%s" 2>/dev/null && cat "%s" && rm "%s"', temp_file, url, temp_file, temp_file ) local handle = io.popen(cmd) if not handle then return nil, "Failed to fetch URL" end local result = handle:read("*a") handle:close() -- Optional: Process and clean the content -- Remove excessive whitespace, clean HTML if needed -- result = result:gsub("%s+", " "):gsub("^\s+", ""):gsub("%s+$", "") return result end, opts = { requires_approval = false, description = "Fetch web content with unlimited size" } }, ["fetch"] = { description = "Fetch a webpage in a markdown format", callback = function(url, options) local cmd = string.format( 'reader -o -i none %s 2>/dev/null', url ) local handle = io.popen(cmd) if not handle then return nil, "Failed to fetch URL" end local result = handle:read("*a") handle:close() return result end, }, -- ["mcp"] = { -- Name this tool whatever you like -- -- Callback provides the necessary functions to CodeCompanion -- callback = function() -- return require("mcphub.extensions.codecompanion") -- end, -- opts = { -- -- If true, CodeCompanion will ask for approval before executing the MCP tool call -- requires_approval = true, -- -- Optional: Pass parameters like temperature to the underlying LLM if the chat strategy supports it -- temperature = 0.7 -- } -- }, groups = { ['plan'] = { description = "Planning my project and codes with enhanced web access", prompt = "Using all the ${tools} at your disposal including enhanced web fetching. Analyze the codes and use it to help me with my query. You have access to unlimited web content fetching.", tools = { "ask_questions", "fetch", -- Backup unlimited fetch "file_search", "grep_search", "list_code_usages", "next_edit_suggestion", "read_file", "cmd_runner", }, opts = { collapse_tools = true, -- Override default fetch behavior for this group tool_config = { fetch_enhanced = { max_length = 500000, -- 500KB for planning tasks chunk_processing = true, }, }, }, }, ['research'] = { description = "Deep research with unlimited web access", prompt = "You are a research assistant with unlimited web access. Using the ${tools} fetch and analyze large documents, research papers, and comprehensive web content.", tools = { "ask_questions", "fetch", "fetch_enhanced", "file_search", "read_file", }, opts = { collapse_tools = false, }, }, -- NEW: Add development group for coding tasks ['dev'] = { description = "Full-stack development with all necessary tools", prompt = "You are an expert full-stack developer. Use ${tools} to understand, modify, and test code. Always gather context before making changes.", tools = { "agents", }, opts = { collapse_tools = true, }, }, }, }, slash_commands = { ["buffer"] = { opts = { provider = "snacks" } }, -- snacks ["file"] = { opts = { provider = "snacks" } }, -- snackes ['git_files'] = { description = 'List git files', ---@param chat CodeCompanion.Chat callback = function(chat) local handle = io.popen 'git ls-files' if handle ~= nil then local result = handle:read '*a' handle:close() chat:add_reference({ content = result }, 'git', '') return vim.notify('Git files added to the chat.') else return vim.notify('No git files available', vim.log.levels.INFO, { title = 'CodeCompanion' }) end end, opts = { contains_code = false, }, }, -- NEW: Add helpful workspace command ["workspace"] = { description = "Add workspace context", callback = function(chat) local cwd = vim.fn.getcwd() local workspace_info = string.format("Working directory: %s", cwd) -- Get project type local files = vim.fn.glob(cwd .. "/*", false, true) for _, file in ipairs(files) do local basename = vim.fn.fnamemodify(file, ":t") if basename == "package.json" then workspace_info = workspace_info .. "\nProject type: Node.js/JavaScript" break elseif basename == "Cargo.toml" then workspace_info = workspace_info .. "\nProject type: Rust" break elseif basename == "go.mod" then workspace_info = workspace_info .. "\nProject type: Go" break end end chat:add_reference({ content = workspace_info }, 'workspace', '') return vim.notify('Workspace context added.') end, opts = { contains_code = false, }, }, }, -- adapter = { name = "abacus", model = "route-llm" }, -- adapter = { name = "opencode", model = "github-copilot/gpt-5.2-codex" }, adapter = { name = "copilot", model = "gpt-5-mini" }, -- adapter = { name = "goose" }, }, inline = { -- adapter = { name = "abacus", model = "route-llm" }, adapter = { name = "copilot", model = "gpt-5.1-codex-mini" }, }, cmd = { adapter = { name = "copilot", model = "gpt-5-mini" }, }, background = { adapter = { name = "copilot", model = "gpt-5-mini" }, }, }, prompt_library = { markdown = { dirs = { vim.fn.getcwd() .. "/.prompts", "~/.agents/prompts", }, }, ["Code Review"] = { interaction = "chat", description = "Review the selected code for improvements", opts = { mapping = "rpc", modes = { "v" }, adapter = { name = "copilot", model = "chatgpt-5.3-codex", }, }, prompts = { { role = "system", content = "You are an expert code reviewer. Analyze the code for: 1) Logic errors 2) Performance issues 3) Security vulnerabilities 4) Best practices 5) Code clarity and maintainability. Provide specific, actionable feedback.", }, { role = "user", content = function(context) local code = require("codecompanion.helpers.actions").get_code(context.start_line, context.end_line) return "Please review this code:\n\n```" .. context.filetype .. "\n" .. code .. "\n```" end, }, }, cc_keywords = { "review", "critique", "smell", "security", "vulnerability", "best practice", "maintainab" }, cc_threshold = 2, }, ["Optimize Performance"] = { interaction = "chat", description = "Get performance optimization suggestions", opts = { mapping = "rpo", modes = { "v" }, adapter = { name = "copilot", model = "chatgpt-5.3-codex", }, }, prompts = { { role = "system", content = "You are a performance optimization expert. Focus on: 1) Time complexity improvements 2) Space efficiency 3) Algorithm optimization 4) Framework-specific optimizations 5) Caching strategies.", }, { role = "user", content = function(context) local code = require("codecompanion.helpers.actions").get_code(context.start_line, context.end_line) return "How can I optimize the performance of this code?\n\n```" .. context.filetype .. "\n" .. code .. "\n```" end, }, }, cc_keywords = { "optimize", "performance", "slow", "latency", "throughput", "memory", "alloc" }, }, ["Debug Analysis"] = { interaction = "chat", description = "Analyze and debug problematic code", opts = { mapping = "rpd", modes = { "v" }, adapter = { name = "copilot", model = "chatgpt-5.3-codex", }, }, prompts = { { role = "system", content = [[You are a debugging expert. Analyze code for: 1. Potential bugs and issues 2. Logic errors 3. Runtime errors 4. Common pitfalls 5. Debugging strategies. 6. Provide specific solutions.]], }, { role = "user", content = function(context) local code = require("codecompanion.helpers.actions").get_code(context.start_line, context.end_line) return string.format( [[Help me debug this code from buffer %d and identify potential issues: ```%s %s ```]], context.bufnr, context.filetype, code ) end, opts = { contains_code = true, } }, }, cc_keywords = { "bug", "debug", "error", "exception", "crash", "stacktrace", "broken", "issue" }, }, ["Architecture Review"] = { interaction = "chat", description = "Review architectural patterns and design", opts = { mapping = "rpa", modes = { "v" }, adapter = { name = "copilot", model = "chatgpt-5.4", }, }, prompts = { { role = "system", content = "You are a software architect. Review code for: 1) Design patterns usage 2) SOLID principles 3) Separation of concerns 4) Scalability considerations 5) Maintainability. Suggest architectural improvements.", }, { role = "user", content = function(context) local code = require("codecompanion.helpers.actions").get_code(context.start_line, context.end_line) return string.format( [[Please review the architecture and design of this code from buffer %d: ```%s %s ```]], context.bufnr, context.filetype, code ) end, opts = { contains_code = true, } }, }, cc_keywords = { "architecture", "design", "pattern", "solid", "scalab", "maintainab", "refactor" }, }, }, display = { action_palette = { provider = "snacks", opts = { show_default_actions = true, show_default_prompt_library = true, } }, chat = { show_settings = false, render_headers = false, icons = { chat_context = "📎️", }, fold_context = true, -- NEW: Better chat window configuration window = { layout = "vertical", width = 0.45, height = 0.8, border = "rounded", -- Better visual }, auto_scroll = false, start_in_insert_mode = false, -- More convenient } } })