--- url: /installation.md description: >- Install CodeCompanion.nvim and its dependencies, then add extensions and the plugins it works with. --- # Installation > \[!IMPORTANT] > Pin the plugin to a release to avoid breaking changes ## Requirements * Neovim 0.12 or later * [curl](https://curl.se) * [plenary.nvim](https://github.com/nvim-lua/plenary.nvim) * *(Optional)* An API key for your chosen LLM * *(Optional)* A Tree-sitter `yaml` parser, for markdown prompts in the [prompt library](/configuration/prompt-library) and [skills](/usage/chat-buffer/skills) * *(Optional)* The [file](https://man7.org/linux/man-pages/man1/file.1.html) command, to detect the mimetype of images * *(Optional)* [ripgrep](https://github.com/BurntSushi/ripgrep), for the [grep\_search](/usage/chat-buffer/agents-tools#grep-search) tool * *(Optional)* [MarkItDown](https://github.com/microsoft/markitdown), to fetch webpages with the default adapter * *(Optional)* `sqlite3`, to read Copilot tokens from a SQLite database To check them: ``` :checkhealth codecompanion ``` ## Installing [nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter) is the easiest way to install the `yaml` parser: ::: code-group ```lua [vim.pack] vim.pack.add({ "https://www.github.com/nvim-lua/plenary.nvim" }) vim.pack.add({ "https://github.com/nvim-treesitter/nvim-treesitter" }) vim.pack.add({ { src = "https://www.github.com/olimorris/codecompanion.nvim", version = vim.version.range("^19.0.0") } }) -- Somewhere in your config require("codecompanion").setup() ``` ```lua [Lazy.nvim] { "olimorris/codecompanion.nvim", version = "^19.0.0", opts = {}, dependencies = { "nvim-lua/plenary.nvim", "nvim-treesitter/nvim-treesitter", }, }, ``` ```lua [Packer.nvim] use({ "olimorris/codecompanion.nvim", tag = "^19.0.0", config = function() require("codecompanion").setup() end, requires = { "nvim-lua/plenary.nvim", "nvim-treesitter/nvim-treesitter", }, }), ``` ::: > \[!WARNING] > If you pin your plugins to their latest releases, keep plenary.nvim on its master branch. See [#377](https://github.com/olimorris/codecompanion.nvim/issues/377) ## Extensions Extensions add features to CodeCompanion. To install and configure [mcphub.nvim](https://github.com/ravitemer/mcphub.nvim): ::: code-group ```lua [1. Install] -- Lazy.nvim { "olimorris/codecompanion.nvim", dependencies = { "ravitemer/mcphub.nvim" } } ``` ```lua [2. Configure] require("codecompanion").setup({ extensions = { mcphub = { callback = "mcphub.extensions.codecompanion", opts = { make_vars = true, make_slash_commands = true, show_result_in_chat = true } } } }) ``` ::: See [Extending with Extensions](/extending/extensions) for the extensions available and how to write your own. ## Other Plugins These plugins work well with CodeCompanion. The examples use lazy.nvim. ### Rendering Markdown [render-markdown.nvim](https://github.com/MeanderingProgrammer/render-markdown.nvim) and [markview.nvim](https://github.com/OXY2DEV/markview.nvim) render the markdown in the chat buffer: ::: code-group ```lua [render-markdown.nvim] { "MeanderingProgrammer/render-markdown.nvim", ft = { "markdown", "codecompanion", "codecompanion_chat_input" } }, ``` ```lua [markview.nvim] { "OXY2DEV/markview.nvim", lazy = false, opts = { preview = { filetypes = { "markdown", "codecompanion", "codecompanion_chat_input" }, ignore_buftypes = {}, }, }, }, ``` ::: ### img-clip.nvim [img-clip.nvim](https://github.com/hakonharnes/img-clip.nvim) pastes images from your clipboard into the chat buffer with `:PasteImage`: ```lua { "HakonHarnes/img-clip.nvim", opts = { filetypes = { codecompanion = { prompt_for_file_name = false, template = "[Image]($FILE_PATH)", use_absolute_path = true, }, }, }, }, ``` ## Completion Completion in the [chat buffer](/usage/chat-buffer/#completion) works with [blink.cmp](https://github.com/Saghen/blink.cmp), [nvim-cmp](https://github.com/hrsh7th/nvim-cmp) and [coc.nvim](https://github.com/neoclide/coc.nvim), or with Neovim's native completion. On blink.cmp 0.10.0 or earlier, add `codecompanion` as a source: ```lua sources = { per_filetype = { codecompanion = { "codecompanion" }, } }, ``` ## Help If something isn't working, see [Troubleshooting](/troubleshooting). --- --- url: /getting-started.md description: >- Go from a fresh install of CodeCompanion to an LLM editing code in your project, and review what it changed. --- # Getting Started Go from a fresh [installation](/installation) to an LLM editing a file in your project, then review what it changed. Each step links to the page that covers it in full. > \[!TIP] > Every example in these docs is wrapped in `require("codecompanion").setup({ ... })` so it can be pasted as-is. With [lazy.nvim](https://github.com/folke/lazy.nvim), put the contents of `setup()` in `opts` instead ## Choosing an Adapter An *adapter* connects CodeCompanion to an LLM or an agent. The default is GitHub Copilot, so if you've signed in with [copilot.vim](https://github.com/github/copilot.vim) or [copilot.lua](https://github.com/zbirenbaum/copilot.lua) there's nothing to configure. Otherwise, pick one. Adapters are set per *interaction*, one of the ways you work with an LLM. `chat` is the chat buffer and `inline` is `:CodeCompanion`, which edits a buffer in place: ::: code-group ```lua [API Key] -- Reads the ANTHROPIC_API_KEY environment variable require("codecompanion").setup({ interactions = { chat = { adapter = "anthropic", -- Or "openai", "gemini", "deepseek", "mistral", "openrouter" and more }, inline = { adapter = "anthropic", }, }, }) ``` ```lua [Local Model] -- Requires Ollama to be running require("codecompanion").setup({ interactions = { chat = { adapter = { name = "ollama", model = "qwen3:8b", }, }, inline = { adapter = { name = "ollama", model = "qwen3:8b", }, }, }, }) ``` ```lua [Agent] -- Requires Claude Code and claude-agent-acp to be installed require("codecompanion").setup({ interactions = { chat = { adapter = "claude_code", -- Or "codex", "gemini_cli", "opencode" and more }, }, }) ``` ::: HTTP adapters look for an API key in an environment variable named after the provider, such as `OPENAI_API_KEY` or `GEMINI_API_KEY`. To read it from somewhere else, like a password manager, see [environment variables](/configuration/adapters-http#environment-variables). An agent, such as Claude Code, runs its own tools and connects over the [Agent Client Protocol](/agent-client-protocol). Each one needs some setup first. For Claude Code, that's [installing it and adding a token](/configuration/adapters-acp#setup-claude-code). Agents only work in the chat buffer. Run `:checkhealth codecompanion` to confirm everything is in place. To compare adapters, see [Choosing an Adapter](/guides/choosing-an-adapter). ## Starting a Chat Open a file in your project and run: ``` :CodeCompanionChat ``` This opens a *chat buffer*. Type a message and send it with `` in insert mode or `` in normal mode. To share the file you opened the chat from, add `#{buffer}` to your message: ```md What does the code in #{buffer} do? ``` `#{buffer}` is *editor context*. Other editor context includes `#{diagnostics}` for LSP errors, `#{selection}` for a visual selection and `#{terminal}` for your latest terminal output. Typing `#` shows everything that's available. See [Editor Context](/usage/chat-buffer/editor-context) for the full list. Run `:CodeCompanionChat Toggle` to hide the chat and bring it back. It keeps its history while it's hidden. ## Letting the LLM Edit Code An LLM can't touch your files until you give it *tools*. Add `@{files}` to your message to let it read, create and edit files: ```md @{files} Add a docstring to each function in #{buffer} ``` Some tools, like reading a file, ask for your approval before they run. The chat buffer lists your options: | Keymap | Description | |---|---| | `g1` | Always accept this tool in this chat | | `g2` | Accept this time | | `g3` | Reject, and tell the LLM why | | `g4` | Cancel this and every other pending tool call | Before an edit is written to the file, it's shown as a diff. Small diffs appear in the chat buffer and larger ones open in a floating window. Press `gv` to open it yourself. Accept the change with `g2` or reject it with `g3`. Once you're happy with the tools, press `gty` to stop being asked each time. See [approval modes](/usage/chat-buffer/agents-tools#approval-modes) for what each mode allows. > \[!NOTE] > Agents like Claude Code bring their own tools, so you don't need `@{files}`. They ask for permission in the chat buffer too `@{agent}` gives the LLM every tool it needs to work through a task by itself, including running commands. See [Tools](/usage/chat-buffer/agents-tools) for the full list. ## Reviewing the Changes Once the LLM has finished, run: ``` :CodeCompanionCodeReview ``` This opens every change made since your first message, file by file and hunk by hunk. Press `ga` to accept a change, `gr` to revert it or `gc` to leave a comment on it. To send your comments back, add `#{code_review}` to your next message: ```md #{code_review} Please address my comments ``` The next review only shows what the LLM changed in response. See [Code Review](/usage/code-review) for the full workflow. ## Editing Inline You don't need a chat buffer for smaller changes. Select some code and run: ``` :'<,'>CodeCompanion Use early returns ``` The LLM edits the selection in place and shows you a diff. Keep it with `g2` or undo it with `g3`. See [Inline](/usage/inline) for more. The [prompt library](/usage/prompt-library) has prompts for common tasks, called by their alias: | Command | Description | |---|---| | `:'<,'>CodeCompanion /explain` | Explain how the selected code works, in a chat buffer | | `:'<,'>CodeCompanion /fix` | Fix the selected code, in a chat buffer | | `:'<,'>CodeCompanion /lsp` | Explain the LSP diagnostics for the selected code, in a chat buffer | | `:CodeCompanion /commit` | Write a commit message for your staged changes, in a chat buffer | Run `:CodeCompanionActions` to browse them, alongside any prompts you write yourself. ## Keymaps CodeCompanion doesn't set any global keymaps. These are the ones the author uses: ```lua vim.keymap.set({ "n", "v" }, "", "CodeCompanionActions", { noremap = true, silent = true }) vim.keymap.set({ "n", "v" }, "a", "CodeCompanionChat Toggle", { noremap = true, silent = true }) vim.keymap.set("v", "ga", "CodeCompanionChat Add", { noremap = true, silent = true }) -- Expand 'cc' into 'CodeCompanion' in the command line vim.cmd([[cab cc CodeCompanion]]) ``` `:CodeCompanionChat Add` adds the visual selection to the current chat. See [Commands](/commands) for every command and its arguments. ## Next Steps * [Coding with an Agent](/guides/coding-with-an-agent) - Use Claude Code, Codex and others from the chat buffer * [Customising the Chat Experience](/guides/customising-the-chat-experience) - Choose where the chat opens and where you write your message * [Controlling Tool Approvals](/guides/tool-approvals) - Decide what an LLM can do without asking * [Sharing Rules and Skills Across Projects](/guides/sharing-rules-and-skills) - Give the LLM your project's conventions, like `AGENTS.md` * [Setting Up Web Search](/guides/web-search) - Let the LLM look things up * [Running Background Tasks on a Cheaper Model](/guides/background-model) - Keep costs down on chat titles and compaction --- --- url: /guides/choosing-an-adapter.md description: >- Pick between GitHub Copilot, an API provider, a local model, an OpenAI-compatible endpoint or an agent for CodeCompanion in Neovim, and set it up. --- # Choosing an Adapter CodeCompanion defaults to GitHub Copilot, which is no help if you don't have it. Every other option works too, and you can mix them across interactions. If you haven't set up the plugin yet, start with [Getting Started](/getting-started). ## The Options An *adapter* connects CodeCompanion to an LLM or an agent. They come in two types: *HTTP* adapters send requests to a model and use CodeCompanion's own [tools](/usage/chat-buffer/agents-tools), whilst *ACP* adapters hand the conversation to an agent that runs its own tools. The [CLI interaction](/usage/cli) sits apart from both, running an agent's own interface in a terminal inside Neovim. | Option | Adapter | What you need | |---|---|---| | GitHub Copilot | `copilot` | A Copilot plan, including Free and Student, signed in with copilot.vim or copilot.lua | | API provider | `anthropic`, `openai`, `gemini`, `deepseek`, `mistral`, `openrouter`, `xai`... | An API key | | Local model | `ollama` | Ollama running on your machine or network | | OpenAI-compatible endpoint | `openai_compatible` | The endpoint's URL, and a key if it requires one | | ACP agent | `claude_code`, `codex`, `gemini_cli`, `opencode`, `copilot_acp`... | The agent installed and signed in | | CLI interaction | Any agent you define | The agent's CLI installed | The first four run CodeCompanion's own tools and work everywhere. An ACP agent runs its own tools and only works in the chat buffer. The CLI interaction runs in its own terminal buffer. ### GitHub Copilot If you pay for Copilot, or have Copilot Free or Student, there's nothing to configure beyond signing in. The default model is `auto`, which lets Copilot pick the model and is the only option on the Free and Student plans. CodeCompanion reads the token that copilot.vim or copilot.lua saves when you sign in. ### An API Provider Choose this when you already pay a provider or want a specific model. Each adapter reads its key from an environment variable named after the provider, such as `ANTHROPIC_API_KEY`, `OPENAI_API_KEY` or `OPENROUTER_API_KEY`. Adapters are set per *interaction*, which is one of the ways you work with an LLM in CodeCompanion. `chat` is the chat buffer and `inline` is `:CodeCompanion`, which edits a buffer in place. To use Anthropic for both: ```lua require("codecompanion").setup({ interactions = { chat = { adapter = "anthropic", }, inline = { adapter = "anthropic", }, }, }) ``` To read the key from a password manager or a file instead, see [environment variables](/configuration/adapters-http#environment-variables). [OpenRouter](/configuration/adapters-http#openrouter) gives you models from many providers behind one key. ### A Local Model Choose this when code can't leave your machine or you don't want to pay per request. The `ollama` adapter connects to `http://localhost:11434`, or `OLLAMA_HOST` if it's set. Small local models are slower and weaker at tool calling than hosted ones, so expect agentic tasks with `@{agent}` to be less reliable. See [Running Local Models](/guides/running-local-models). ### An OpenAI-Compatible Endpoint Choose this for a self-hosted server like llama.cpp or vLLM, or a company gateway that speaks the OpenAI API. There's no preset for it, so you extend `openai_compatible` with your URL. See [Connecting an OpenAI-Compatible Provider](/guides/openai-compatible-providers). ### An ACP Agent Choose this when you already use Claude Code, Codex or another agent and want it in a chat buffer. The agent runs its own tools, so `@{files}` and the other CodeCompanion tools aren't offered, and it can bill against an existing subscription such as Claude Pro. Each agent needs installing and authenticating first. See [Coding with an Agent](/guides/coding-with-an-agent) and the setup steps in [ACP Adapters](/configuration/adapters-acp). > \[!NOTE] > ACP adapters only work in the chat buffer. Inline, cmd and background interactions need an HTTP adapter ### The CLI Interaction Choose this when you'd rather use the agent's own interface, with CodeCompanion sending it prompts and context from your buffers. Nothing is set up by default, so you define the agent yourself. See [Using the CLI](/usage/cli) and [Configuring the CLI](/configuration/cli). ## Choosing a Model Every adapter has a default model. To use a different one, pass a table with the adapter's `name` and a `model` instead of just the name: ```lua require("codecompanion").setup({ interactions = { chat = { adapter = { name = "anthropic", model = "claude-opus-5-5", }, }, }, }) ``` This works for agents too, such as `{ name = "claude_code", model = "opus" }`. For an agent's mode and other options, see [Choosing a Default Model and Mode](/guides/coding-with-an-agent#choosing-a-default-model-and-mode). Press `ga` in a chat buffer to see which models an adapter offers. Copilot, Anthropic, Mistral, OpenRouter, Hugging Face, Novita, Ollama and `openai_compatible` fetch the list from the provider, so it matches what your account can use. The others list a fixed set, but any model name the provider accepts works. See [Changing the Default Model](/configuration/adapters-http#changing-the-default-model) to set a model on the adapter itself rather than per interaction. ## Mixing Adapters CodeCompanion has four interactions that use an adapter: * **Chat** - The chat buffer (`:CodeCompanionChat`) * **Inline** - Edits a buffer in place (`:CodeCompanion`) * **Cmd** - Writes a command in the command-line (`:CodeCompanionCmd`) * **Background** - Runs tasks without any input from you, such as naming a chat Each has its own adapter, so you can pair an agent for long tasks in the chat buffer with a fast HTTP adapter for inline edits, and keep background tasks on something cheap: ```lua require("codecompanion").setup({ interactions = { chat = { adapter = "claude_code", }, inline = { adapter = "anthropic", }, cmd = { adapter = "anthropic", }, background = { adapter = { name = "ollama", model = "qwen3:8b", }, }, }, }) ``` **Any interaction you leave unset stays on `copilot`**, and fails without it. See [Running Background Tasks on a Cheaper Model](/guides/background-model) for the background interaction. ## Switching in a Chat Press `ga` in a chat buffer to pick a different adapter, then a model. **Once the LLM has called a tool or returned reasoning, you can only change the model, not the adapter.** Start a new chat instead. > \[!NOTE] > `ga` is disabled when `display.chat.show_settings = true`. Change the model in the settings at the top of the chat buffer instead To open a chat with a specific adapter and model: ``` :CodeCompanionChat adapter=openai model=gpt-4.1 ``` For an ACP adapter, `command=` picks one of the adapter's commands instead of a model, such as `:CodeCompanionChat adapter=gemini_cli command=yolo`. Inline prompts take the adapter too, with `:CodeCompanion adapter=anthropic `. See [Commands](/commands) for the rest. ## Hiding Adapters The `ga` picker and command completion list every preset. To remove the ones you never use: ```lua require("codecompanion").setup({ adapters = { http = { opts = { hidden = { azure_openai = true, huggingface = true, novita = true }, }, }, }, }) ``` Hidden adapters can still be set in your config and resolved by name. Your list is merged with the default one, which already hides `duckduckgo`, `jina`, `markitdown` and `tavily`. To show only the adapters you've defined yourself, set `show_presets = false` under `adapters.http.opts` or `adapters.acp.opts`. See [Hiding Adapters](/configuration/adapters-http#hiding-adapters) and [Hiding Preset Adapters](/configuration/adapters-acp#hiding-preset-adapters). --- --- url: /guides/coding-with-an-agent.md description: >- Use Claude Code, Codex and other agents in a CodeCompanion chat buffer via the Agent Client Protocol, including choosing a default model and mode. --- # Coding with an Agent An agent such as Claude Code or Codex brings its own tools, permissions and slash commands, but normally lives in a separate terminal. CodeCompanion runs the agent over the [Agent Client Protocol](/agent-client-protocol) (ACP), so the conversation, its permission requests and its edits all happen in a chat buffer. This guide uses Claude Code throughout. The steps are the same for any other agent once it's installed. ## Installing Claude Code 1. [Install Claude Code](https://docs.anthropic.com/en/docs/claude-code/quickstart) 2. Install [claude-agent-acp](https://github.com/zed-industries/claude-agent-acp), which lets CodeCompanion talk to Claude Code: ``` npm install -g @zed-industries/claude-agent-acp ``` 3. Authenticate with your Claude subscription or an API key: ::: code-group ```lua [Subscription] -- Run `claude setup-token` in your terminal to get the token require("codecompanion").setup({ adapters = { acp = { extend = { claude_code = { env = { CLAUDE_CODE_OAUTH_TOKEN = "cmd:op read op://personal/Claude/token --no-newline", }, }, }, }, }, }) ``` ```lua [API Key] require("codecompanion").setup({ adapters = { acp = { extend = { claude_code = { env = { ANTHROPIC_API_KEY = "cmd:op read op://personal/Anthropic/credential --no-newline", }, }, }, }, }, }) ``` ::: If `CLAUDE_CODE_OAUTH_TOKEN` is already exported in your shell, skip step 3. See [Setup: Claude Code](/configuration/adapters-acp#setup-claude-code) for screenshots of the token flow, and [environment variables](/configuration/adapters-http#environment-variables) for other ways to supply a secret. For other agents, follow their setup section instead: [Codex](/configuration/adapters-acp#setup-codex), [Gemini CLI](/configuration/adapters-acp#setup-gemini-cli), [OpenCode](/configuration/adapters-acp#setup-opencode), [Copilot CLI](/configuration/adapters-acp#setup-copilot-cli) and [others](/configuration/adapters-acp). ## Making It the Chat Adapter To use Claude Code for every chat: ```lua require("codecompanion").setup({ interactions = { chat = { adapter = "claude_code", -- Or "codex", "gemini_cli", "opencode", "copilot_acp"... }, }, }) ``` To try it without changing your config, run: ``` :CodeCompanionChat adapter=claude_code ``` The agent takes a few seconds to start. Once it's connected, you talk to it like any other chat, and send with `` or ``. ## Choosing a Default Model and Mode When a session starts, the agent tells CodeCompanion which *session config options* it has, such as the model, the mode and the reasoning level. Every chat starts on the agent's own defaults unless you set your own. The model can be set alongside the adapter: ```lua require("codecompanion").setup({ interactions = { chat = { adapter = { name = "claude_code", model = "opus", }, }, }, }) ``` The mode, and every other option, goes in the adapter's `defaults.session_config_options`: ```lua require("codecompanion").setup({ adapters = { acp = { extend = { claude_code = { defaults = { session_config_options = { model = "opus", mode = "acceptEdits", }, }, }, }, }, }, interactions = { chat = { adapter = "claude_code", }, }, }) ``` Each key is an option's category (`model`, `mode`, `thought_level`) and each value is matched against the agent's values or their display names, ignoring case. The model is always set first, as some agents change their other options depending on the model. If a value doesn't match, CodeCompanion logs a warning and the chat stays on the agent's default. To see which options and values your agent offers, press `gd` in a chat buffer to open the [debug window](/usage/chat-buffer/#debug-window), or run `/acp_session_options`. > \[!NOTE] > If you set the model in both places, `session_config_options` takes precedence ## Changing Model and Mode Mid-Chat | Keymap or Command | Action | |---|---| | `ga` | Change adapter, then pick a model from the agent's list | | `/acp_session_options` | Change any session config option, such as the mode or reasoning level | | `/command` | Restart the agent with another command from the adapter's `commands`, such as Claude Code's `yolo`, which starts a new conversation | The current model and option values are marked with `*`. ## Permissions The agent decides when it needs your permission, and CodeCompanion shows the request in the chat buffer. Only the options the agent offers are listed: | Keymap | Action | |---|---| | `gv` | View the proposed edit in a floating diff | | `g1` | Always accept. The agent remembers this, not CodeCompanion | | `g2` | Accept this time | | `g3` | Reject | | `g4` | Cancel the request | A proposed edit of six lines or fewer is shown in the chat buffer. A larger one opens in a floating diff if the chat buffer is visible, where the same keymaps apply and `}` and `{` move between hunks. See [Diff](/configuration/diff) to change the threshold. Your chat's [approval mode](/usage/chat-buffer/agents-tools#approval-modes) also applies to the agent. Press `gty` to choose one: * **Ask** - Every request is shown to you * **Auto** - Reads, searches, fetches, edits and safe shell commands are accepted for you. Everything else is shown to you * **YOLO** - Every request is accepted for you A request accepted by a mode is only ever accepted once, so the agent doesn't keep the permission after you leave that mode. See [Controlling Tool Approvals](/guides/tool-approvals) for what counts as a safe command. ## Using the Agent's Slash Commands The agent's own slash commands are triggered with `\`, which keeps them apart from CodeCompanion's `/` [slash commands](/usage/chat-buffer/slash-commands): ```md \compact ``` CodeCompanion changes `\compact` to `/compact` before sending it. Commands are discovered from the agent, so they appear in completion one to five seconds after the chat opens. Not every command the agent has in its terminal is available over ACP. ## Sharing Context [Editor context](/usage/chat-buffer/editor-context) and CodeCompanion's slash commands work as they do with any other adapter: ```md Why does #{buffer} fail on the errors in #{diagnostics}? ``` **A buffer or file is shared as its path, and the agent reads it from disk, so save your changes first.** Everything else, such as `#{selection}`, `#{diagnostics}` and `#{terminal}`, is sent as text. An image added with `/file` is sent as an image to agents that accept them, which includes Claude Code. ## Passing MCP Servers to the Agent To give the agent the [MCP servers](/configuration/mcp) you've configured for CodeCompanion, or a separate list of its own: ::: code-group ```lua [Inherit] require("codecompanion").setup({ adapters = { acp = { extend = { claude_code = { defaults = { mcpServers = "inherit_from_config", }, }, }, }, }, mcp = { servers = { ["sequential-thinking"] = { cmd = { "npx", "-y", "@modelcontextprotocol/server-sequential-thinking" }, }, }, opts = { default_servers = { "sequential-thinking" }, }, }, }) ``` ```lua [Manual] require("codecompanion").setup({ adapters = { acp = { extend = { claude_code = { defaults = { mcpServers = { { name = "sequential-thinking", command = "npx", args = { "-y", "@modelcontextprotocol/server-sequential-thinking" }, env = {}, }, }, }, }, }, }, }, }) ``` ::: With `inherit_from_config`, only the servers in `mcp.opts.default_servers` are passed, and only while `mcp.opts.acp_enabled` is `true`, the default. The agent starts and runs them itself, so they don't appear in the chat buffer's context. See [Configuring MCP Servers](/configuration/adapters-acp#configuring-mcp-servers) for more. ## Reviewing the Agent's Work Once the agent has finished, run: ``` :CodeCompanionCodeReview ``` This shows every change made since your first message, and lets you accept, revert or comment on each hunk. Send your comments back with `#{code_review}` in your next message. See [Reviewing an Agent's Changes](/guides/reviewing-changes) for the full workflow. ## ACP or the CLI Interaction `:CodeCompanionCLI` runs the agent's own terminal interface inside Neovim, rather than a chat buffer. See [CLI](/usage/cli) to set it up. | | ACP | CLI | |---|---|---| | Where you talk to the agent | A chat buffer | The agent's own interface, in a Neovim terminal | | Permission requests | In the chat buffer, with CodeCompanion's approval modes | In the agent's interface | | Agent features | Only what the agent exposes over ACP | Everything the agent has | | Switching agent or model | `ga` in the same chat | Start another CLI interaction | | Sharing context | Editor context and slash commands in the prompt | Editor context sent from any buffer with `require("codecompanion").cli()` | Choose ACP if you want to read, search and yank the conversation like any other buffer. Choose the CLI if you rely on a feature the agent doesn't expose over ACP, such as its plan view or a particular slash command. ## Limitations * Agents only work in the chat buffer, not in the [inline](/usage/inline) interaction or for background tasks * CodeCompanion's own tools, such as `@{files}` and `@{agent}`, aren't offered to an agent. It uses its own * CodeCompanion's system prompt isn't sent to the agent * CodeCompanion doesn't [compact](/configuration/context-management) an agent's conversation. The agent manages its own context, and `\compact` asks it to * `/save`, `/rename` and `/fork` aren't available. The agent keeps its own sessions, which `/resume` lists in a new chat if the agent supports it * An agent's plan isn't shown in the chat buffer, and agents can't use a Neovim terminal. See [ACP Support](/agent-client-protocol#limitations) --- --- url: /guides/customising-the-chat-experience.md description: >- Shape CodeCompanion's chat buffer around how you work, from where it opens to where you write and how you follow a response. --- # Customising the Chat Experience CodeCompanion's chat buffer opens beside your code and takes your message at the bottom of the conversation. That suits most people, but you can move it, write in a separate split and keep working whilst a response streams. Each section below starts with the situation it suits. For every option, see [Configuring the Chat Buffer UI](/configuration/ui). ## Where the Chat Opens The chat opens in a vertical split by default. Choose a layout under `display.chat.window` to suit your screen: | Layout | Suits | |---|---| | `vertical` | A wide screen, with code and chat side by side | | `horizontal` | A narrow or portrait screen, with the chat above or below your code | | `float` | Dipping in for a quick question, then hiding it again | | `tab` | Long conversations that deserve the whole screen | | `buffer` | Treating the chat like any other buffer in the current window | To open the chat in a split beneath your code: ```lua require("codecompanion").setup({ display = { chat = { window = { layout = "horizontal", -- Can be "float", "vertical", "horizontal", "tab" or "buffer" position = "bottom", -- Can be "left", "right", "top" or "bottom" height = 0.4, }, }, }, }) ``` If you work across several tabs, `pertab = true` gives each tab its own chat window, whilst `sticky = true` moves the one chat window with you as you switch tabs. With the `vertical` layout, `full_height = false` splits only the current window rather than the whole height of the editor. See [Layout](/configuration/ui#layout) for the full list of options. ## Writing Your Message By default, you write your message at the bottom of the chat buffer, beneath the conversation. That's the simplest setup, but your message shares a buffer with a response that's still streaming in. If you'd rather keep what you're writing apart from the conversation, write it in a separate split beneath the chat window: ```lua require("codecompanion").setup({ display = { chat = { input = { layout = "split", -- Can be "buffer" or "split" height = 8, -- lines }, }, }, }) ``` When you send it, your message is added to the chat buffer under your header. With the split: * **Chat buffer** - It's read-only, and the chat settings from `display.chat.show_settings` aren't shown at the top * **Filetype** - The input is a `codecompanion_chat_input` buffer, so it can be targeted separately from the chat buffer in your statusline or autocmds * **Keymaps** - Every chat buffer keymap works in both windows. Pressing `i`, `a`, `I`, `A`, `o` or `O` in the chat buffer moves you to the input * **Window** - Hiding or closing either window closes both. If you resize the input, that chat keeps the new height until you restart Neovim. New chats open at `height` ## Working Whilst It Streams A long response or a run of tool calls can take a while, and you may think of your next message before it finishes. With the split, write and send it as normal. **A message sent whilst the LLM is responding is queued**, then sent once it's safe to do so. Sending another adds it to the same queued message. Stopping the response moves the queued message back into the input, above anything you're writing. With the default layout, press `gm` whilst the LLM is responding to send a follow-up, which CodeCompanion delivers in the same way. See [Using the Chat Buffer](/usage/chat-buffer/). ## Moving Through a Conversation The chat buffer follows the response as it streams in. Move the cursor away to read something earlier and it stops following, and moving back to the bottom starts it again. To turn this off: ```lua require("codecompanion").setup({ display = { chat = { auto_scroll = false, }, }, }) ``` `[[` and `]]` move between messages in the chat buffer, even when pressed in the input. With the split, the input also has keymaps to scroll the chat buffer without leaving it, in normal and insert mode: | Keymap | Name | Description | |---|---|---| | `` | `scroll_chat_down` | Scroll the chat down half a page | | `` | `scroll_chat_up` | Scroll the chat up half a page | | `` | `scroll_chat_jump` | Jump to the bottom of the chat, or to the top if you're already at the bottom | Scrolling up stops the chat following the response, and jumping back to the bottom starts it again. To change these keymaps, see [Keymaps](/configuration/chat-buffer#keymaps). ## Context Context you share, such as a buffer or a file, is listed in a block above your message. With the default layout, the block sits under your header in the chat buffer. If a lot of context pushes your message too far from the last response, fold it into a single line with `display.chat.fold_context = true`. See [Context](/configuration/ui#context). With the split, the block sits at the top of the input and stays there between messages. `fold_context` folds the items there too, leaving the header visible. Delete a line to remove that item from the chat. ## Limitations * The input split isn't available for the `float` layout, which keeps writing at the bottom of the chat buffer * On macOS, your terminal must send the Option key as Meta for ``, `` and `` to reach Neovim --- --- url: /guides/running-local-models.md description: >- Run CodeCompanion against a local model in Neovim with Ollama, including choosing the model, connecting to another machine and using tools. --- # Running Local Models A local model keeps your code on your own machine and needs no API key. CodeCompanion ships with an `ollama` adapter, and can reach other local servers such as llama.cpp and LM Studio through `openai_compatible`. This guide assumes [Ollama](https://ollama.com) is installed and running, and that you've pulled a model: ``` ollama pull qwen3:8b ``` ## Pointing CodeCompanion at Ollama Set the adapter and the model for each interaction you want to run locally: ```lua require("codecompanion").setup({ interactions = { chat = { adapter = { name = "ollama", model = "qwen3:8b", }, }, inline = { adapter = { name = "ollama", model = "qwen3:8b", }, }, }, }) ``` The model is the tag you see in `ollama list`. In the chat buffer, press `ga` to switch to any other model you've pulled. ## Choosing the Model **Always set a model.** If you set `adapter = "ollama"` on its own, CodeCompanion asks Ollama for your installed models and picks one of them, and which one it picks isn't predictable. With several models pulled, you can end up in a chat with the wrong one. Set it per interaction, as above, or once on the adapter so every interaction uses it: ::: code-group ```lua [Per Interaction] require("codecompanion").setup({ interactions = { chat = { adapter = { name = "ollama", model = "qwen3-coder:30b", }, }, inline = { adapter = { name = "ollama", model = "qwen3:8b", }, }, }, }) ``` ```lua [On the Adapter] require("codecompanion").setup({ adapters = { http = { ollama = function() return require("codecompanion.adapters").extend("ollama", { schema = { model = { default = "qwen3:8b", }, }, }) end, }, }, interactions = { chat = { adapter = "ollama", }, inline = { adapter = "ollama", }, }, }) ``` ::: A model set on the interaction takes precedence over the adapter's default. > \[!IMPORTANT] > When you use the table form, always include `model`. `adapter = { name = "ollama" }` on its own throws an error when you open a chat ## Running Ollama on Another Machine The adapter connects to `http://localhost:11434` unless the `OLLAMA_HOST` environment variable is set, the same variable the Ollama CLI reads: ```bash export OLLAMA_HOST="http://192.168.1.100:11434" ``` To set the URL in your config instead, or to send an API key to a server that sits behind authentication: ```lua require("codecompanion").setup({ adapters = { http = { ollama = function() return require("codecompanion.adapters").extend("ollama", { env = { url = "https://ollama.example.com", api_key = "OLLAMA_API_KEY", }, headers = { ["Content-Type"] = "application/json", ["Authorization"] = "Bearer ${api_key}", }, }) end, }, }, }) ``` `api_key` names an environment variable. See [environment variables](/configuration/adapters-http#environment-variables) for reading it from a command or password manager instead. ## Using Tools Tools such as `@{files}` and `@{agent}` only work with models that support tool calling. CodeCompanion asks Ollama for each model's capabilities, and if `tools` isn't among them, the tool definitions aren't sent. The model then answers without calling anything. To check a model: ``` ollama show qwen3:8b ``` Models listing `tools` include `qwen3`, `qwen3-coder`, `gpt-oss`, `devstral` and `llama3.1`. Support alone isn't enough. Smaller models often call the wrong tool, pass malformed arguments or stop halfway through a task, so for `@{agent}` a model of 20B parameters or more is a sensible starting point. See [Tools](/usage/chat-buffer/agents-tools) for what each tool does. ### Models That Need One System Prompt Adding tools puts several *system messages* into the request, and some of them sit after your first message. Some chat templates reject this. Qwen3.5 on llama.cpp, for example, fails with `System message must be at the beginning`. The `replace_main_system_prompt` option under `interactions.chat.tools.opts.system_prompt` removes one of these messages, but not all of them. To send a single system message at the top of every request, merge them in the adapter: ```lua require("codecompanion").setup({ adapters = { http = { ollama = function() local ollama = require("codecompanion.adapters.http.ollama") local adapter_utils = require("codecompanion.adapters.utils") return require("codecompanion.adapters").extend("ollama", { handlers = { form_messages = function(self, messages) return ollama.handlers.form_messages(self, adapter_utils.merge_system_messages(messages)) end, }, }) end, }, }, }) ``` For an `openai_compatible` adapter, call `require("codecompanion.adapters.http.openai_compatible").handlers.form_messages` in the same way. ## Keeping It Fast A local model is limited by your hardware. These schema options help: | Option | Effect | |---|---| | `num_ctx` | Size of the context window Ollama loads the model with, in tokens. Larger costs more memory | | `think` | Turns reasoning on or off. On by default for models that support it | | `keep_alive` | How long Ollama keeps the model in memory after a request, such as `"30m"`. Ollama's default is 5 minutes | Set them on the adapter: ```lua require("codecompanion").setup({ adapters = { http = { ollama = function() return require("codecompanion.adapters").extend("ollama", { schema = { num_ctx = { default = 32768, }, think = { default = false, }, keep_alive = { default = "30m", }, }, }) end, }, }, }) ``` [Context management](/configuration/context-management) works out its default triggers as a percentage of the model's maximum context window, as reported by Ollama, not your `num_ctx`. If you set `num_ctx` lower than the maximum, use token counts instead: ```lua require("codecompanion").setup({ interactions = { chat = { opts = { context_management = { editing = { trigger = 20000, -- tokens }, compaction = { trigger = 26000, -- tokens }, }, }, }, }, }) ``` Compaction also runs on the chat's model by default. To send it to a smaller one, see [Running Background Tasks on a Cheaper Model](/guides/background-model). ## Other Local Servers llama.cpp, LM Studio, vLLM and similar servers speak the OpenAI API, so they connect through the `openai_compatible` adapter. See [Connecting an OpenAI-Compatible Provider](/guides/openai-compatible-providers) for the setup, and the [llama.cpp example](/configuration/adapters-http#llama-cpp-with-reasoning-format-deepseek) for showing its reasoning output in the chat buffer. ## Limitations * Opening a chat asks Ollama for its models. If Ollama isn't running or can't be reached, you see an error, and opening the chat waits up to 3 seconds before giving up * Without a `model` set, the model CodeCompanion picks isn't predictable * CodeCompanion doesn't merge system messages for you. Models whose templates need a single system message fail once tools are added, until you use the [workaround above](#models-that-need-one-system-prompt) * Whether a model can use tools comes from Ollama. A model that reports `tools` can still be too small to use them reliably --- --- url: /guides/openai-compatible-providers.md description: >- Connect CodeCompanion to any provider that speaks the OpenAI API, such as a company gateway, LM Studio or vLLM, without writing an adapter. --- # Connecting an OpenAI-Compatible Provider Many providers, self-hosted servers and company gateways accept the same requests as OpenAI's Chat Completions API. CodeCompanion's `openai_compatible` adapter speaks that API, so you only need to give it a URL, a key and a name. ## Adding the Adapter Extend `openai_compatible` under a name of your own. The name is what you pick in the chat buffer and set in your config: ::: code-group ```lua [Company Gateway] require("codecompanion").setup({ adapters = { http = { acme_gateway = function() return require("codecompanion.adapters").extend("openai_compatible", { formatted_name = "Acme Gateway", env = { url = "https://llm-gateway.acme.internal", api_key = "ACME_GATEWAY_KEY", }, schema = { model = { default = "gpt-4.1", }, }, }) end, }, }, }) ``` ```lua [LM Studio] require("codecompanion").setup({ adapters = { http = { lm_studio = function() return require("codecompanion.adapters").extend("openai_compatible", { formatted_name = "LM Studio", env = { url = "http://localhost:1234", api_key = "lm-studio", }, }) end, }, }, }) ``` ```lua [vLLM] require("codecompanion").setup({ adapters = { http = { vllm = function() return require("codecompanion.adapters").extend("openai_compatible", { formatted_name = "vLLM", env = { url = "http://gpu-box.local:8000", api_key = "VLLM_API_KEY", }, }) end, }, }, }) ``` ::: The adapter builds its URLs from four `env` keys: | Key | Default | Purpose | |---|---|---| | `url` | `http://localhost:11434` | The server's base URL, without `/v1` | | `chat_url` | `/v1/chat/completions` | Appended to `url` for every chat and inline request | | `models_endpoint` | `/v1/models` | Appended to `url` to list the available models | | `api_key` | `OPENAI_API_KEY` | Sent as `Authorization: Bearer ` | If a provider gives you a base URL that ends in `/v1`, such as `https://api.tsubasa.sh/v1`, drop the `/v1` from `url`. The default `chat_url` and `models_endpoint` already include it. `formatted_name` is what appears in the chat buffer's header and the [debug window](/usage/chat-buffer/#debug-window). Without it, every adapter built on `openai_compatible` is labelled "OpenAI Compatible". ## Choosing the Model If you don't set a model, the adapter asks `models_endpoint` for the list of models and uses the first one it returns. The list is cached for 30 minutes, set by `adapters.http.opts.cache_models_for` in seconds. Some providers don't serve a model list. If yours returns a 404, or lists models you can't use, set both the default and the choices yourself so the endpoint is never called: ```lua require("codecompanion").setup({ adapters = { http = { tsubasa = function() return require("codecompanion.adapters").extend("openai_compatible", { formatted_name = "Tsubasa", env = { url = "https://api.tsubasa.sh", api_key = "TSUBASA_API_KEY", }, schema = { model = { default = "tsubasa-fast", choices = { "tsubasa-fast", "tsubasa-pro" }, }, }, }) end, }, }, }) ``` Setting only `default` picks the model, but the endpoint is still called when you open the model picker. ## Keeping the API Key Out of Your Config `api_key` is resolved when a request is sent, so the key itself never needs to be in your config. Give it the name of an environment variable, a command or a file: ::: code-group ```lua [Environment Variable] require("codecompanion").setup({ adapters = { http = { acme_gateway = function() return require("codecompanion.adapters").extend("openai_compatible", { env = { url = "https://llm-gateway.acme.internal", api_key = "ACME_GATEWAY_KEY", }, }) end, }, }, }) ``` ```lua [Command] require("codecompanion").setup({ adapters = { http = { acme_gateway = function() return require("codecompanion.adapters").extend("openai_compatible", { env = { url = "https://llm-gateway.acme.internal", api_key = "cmd:op read op://work/Acme Gateway/credential --no-newline", }, }) end, }, }, }) ``` ```lua [File] require("codecompanion").setup({ adapters = { http = { acme_gateway = function() return require("codecompanion.adapters").extend("openai_compatible", { env = { url = "https://llm-gateway.acme.internal", api_key = "file:~/.config/acme/gateway_key", }, }) end, }, }, }) ``` ::: A value that isn't an environment variable, a `cmd:` or a `file:` is sent as it is. That's why `api_key = "lm-studio"` works for a local server that ignores the key. Commands time out after 20 seconds, set by `adapters.opts.cmd_timeout` in milliseconds. See [environment variables](/configuration/adapters-http#environment-variables) for every form `env` accepts. > \[!IMPORTANT] > If you don't set `api_key`, the adapter sends the value of `OPENAI_API_KEY`. Set it to something else so your OpenAI key isn't sent to another provider ## Using It in Chat and Inline To make your adapter the default: ```lua require("codecompanion").setup({ interactions = { chat = { adapter = "acme_gateway", }, inline = { adapter = { name = "acme_gateway", model = "gpt-4.1-mini", }, }, }, }) ``` Every adapter you define under `adapters.http` is listed when you press `ga` in the chat buffer, alongside the presets. To show only your own, set `adapters.http.opts.show_presets = false`. See [Configuring HTTP Adapters](/configuration/adapters-http#hiding-adapters). ## When the Provider Differs ### Extra Headers `headers` are merged with the adapter's own, so `Content-Type` and `Authorization` stay in place. Headers can reference any `env` key with `${key}`: ```lua require("codecompanion").setup({ adapters = { http = { acme_gateway = function() return require("codecompanion.adapters").extend("openai_compatible", { env = { url = "https://llm-gateway.acme.internal", api_key = "ACME_GATEWAY_KEY", team_id = "ACME_TEAM_ID", }, headers = { ["X-Team-Id"] = "${team_id}", }, }) end, }, }, }) ``` ### A Different Endpoint Path Gateways often mount the API under a prefix. Change `chat_url` and `models_endpoint` to match: ```lua require("codecompanion").setup({ adapters = { http = { acme_gateway = function() return require("codecompanion.adapters").extend("openai_compatible", { env = { url = "https://api.acme.com", api_key = "ACME_GATEWAY_KEY", chat_url = "/llm/openai/v1/chat/completions", models_endpoint = "/llm/openai/v1/models", }, }) end, }, }, }) ``` ### No Streaming, Tools or Images The adapter assumes the server supports streaming, tool calls and images. Turn off whatever yours doesn't: ```lua require("codecompanion").setup({ adapters = { http = { acme_gateway = function() return require("codecompanion.adapters").extend("openai_compatible", { env = { url = "https://llm-gateway.acme.internal", api_key = "ACME_GATEWAY_KEY", }, opts = { stream = false, tools = false, vision = false, }, }) end, }, }, }) ``` With `tools = false`, tool definitions are left out of the request, so the LLM never sees `@{files}` or any other tool. With `vision = false`, images added with [/file](/usage/chat-buffer/slash-commands#file) aren't added to the chat. ### The Responses API If your provider or gateway serves OpenAI's [Responses API](https://platform.openai.com/docs/api-reference/responses) at `/v1/responses`, extend `openai` instead. CodeCompanion uses it to carry a model's reasoning between turns and for server-side compaction. It has no `env.url`, so set the full `url`: ```lua require("codecompanion").setup({ adapters = { http = { acme_responses = function() return require("codecompanion.adapters").extend("openai", { formatted_name = "Acme Gateway (Responses)", url = "https://llm-gateway.acme.internal/v1/responses", env = { api_key = "ACME_GATEWAY_KEY", }, }) end, }, }, }) ``` ## When You Need a Custom Adapter "OpenAI-compatible" sometimes only means close. The HAWKI API, for example, expects the model and messages wrapped in a `payload` object, and rejects the request with a 422 until they are. When a provider changes the shape of the request or the response, you need your own handlers. See [Extending with Adapters](/extending/adapters). To see exactly what's being sent, set `opts.log_level = "DEBUG"` and send a message. The log records the path of each request body, and at that level the file is kept on disk. `:checkhealth codecompanion` shows where the log file is. ## Limitations * Every adapter built on `openai_compatible` shares one model cache. If you define two, the second can be offered the first server's models until the cache expires, so set `schema.model.default` and `choices` on each * `opts` apply to the whole adapter, not per model. To mix models that do and don't support tools on one server, define an adapter for each See [Choosing an Adapter](/guides/choosing-an-adapter) to compare this with the built-in adapters, or [Running Local Models](/guides/running-local-models) for Ollama. --- --- url: /guides/tool-approvals.md description: >- Decide which tools an LLM can run in CodeCompanion without asking, and which need your approval first. --- # Controlling Tool Approvals Once an LLM has [tools](/usage/chat-buffer/agents-tools), it can read files, edit code and run shell commands in your project. CodeCompanion lets you choose which of those it does freely and which it has to ask you about, per tool, per command and per chat buffer. This page is organised around what you want to allow. For every option, see the [approvals reference](/usage/chat-buffer/agents-tools#approvals) and [tool configuration](/configuration/tools#approvals). ## What Asks by Default Out of the box, every chat buffer starts in *Ask* mode and the built-in tools behave like this: | Tool | Before it runs | After it runs | |---|---|---| | `read_file`, `grep_search`, `memory` | Asks | - | | `run_command` | Asks, for each command | - | | `delete_file` | Asks, for each file | - | | `create_file`, `edit_file`, `overwrite_file` | Runs | Asks you to confirm the change | | `file_search`, `get_changed_files`, `get_diagnostics`, `fetch_webpage`, `search_help`, `web_search` | Runs | - | Approvals are remembered per chat buffer. Approving a tool in one chat doesn't approve it anywhere else. ## Answering a Prompt When a tool needs approval, the chat buffer lists your options: | Keymap | Option | What happens | |---|---|---| | `g1` | Always accept | Runs the tool, and doesn't ask again in this chat | | `g2` | Accept | Runs the tool this once | | `g3` | Reject | Skips the tool and asks you for a reason to send to the LLM | | `g4` | Cancel | Skips this tool and every other pending tool call | For `run_command` and `delete_file`, **Always accept only covers that exact command or file**. Approving `make format` doesn't approve `make test`. After `create_file`, `edit_file` or `overwrite_file` produces a change, you're asked to confirm it. Small diffs are shown in the chat buffer and larger ones open in a floating window, which `gv` opens on demand. The same `g1` to `g4` keymaps apply, except that Cancel only discards this change. Always accept here means later edits from that tool in this chat are written without a diff. These keymaps are shared across CodeCompanion and can be changed under `interactions.shared.keymaps`. See [Keymaps](/keymaps). ## Approval Modes Press `gty` in the chat buffer to choose a mode for that chat: | Mode | What runs without asking | |---|---| | Ask | Only the tools that don't require approval, as in the table above | | Auto | Everything except `run_command`, which asks unless the command is on its safe list, and protected tools such as `delete_file` | | YOLO | Everything | Auto mode also skips the confirmation after an edit, so changes are written straight to disk. Use [Code Review](/usage/code-review) to check them afterwards. To start every chat buffer in a different mode: ```lua require("codecompanion").setup({ interactions = { chat = { tools = { opts = { approval_mode = "auto", -- Can be "ask", "auto" or "yolo" }, }, }, }, }) ``` > \[!WARNING] > YOLO runs every tool without asking, including `delete_file` and any shell command. Only use it where you can recover from lost data ## Scenarios ### Editing Freely, Asking Before Commands Auto mode already does this, and asks before deletions too. Emptying the safe list makes every shell command ask. Alternatively, stay in Ask mode and remove the prompts you don't want: ::: code-group ```lua [Auto Mode] require("codecompanion").setup({ interactions = { chat = { tools = { ["run_command"] = { opts = { safe_commands = {}, }, }, opts = { approval_mode = "auto", }, }, }, }, }) ``` ```lua [Ask Mode] require("codecompanion").setup({ interactions = { chat = { tools = { ["edit_file"] = { opts = { require_confirmation_after = false, }, }, ["read_file"] = { opts = { require_approval_before = false, }, }, }, }, }, }) ``` ::: `edit_file` treats files that are open in Neovim differently from those that aren't. To be asked before the LLM edits a file you don't have open, but not a buffer you do: ```lua require("codecompanion").setup({ interactions = { chat = { tools = { ["edit_file"] = { opts = { require_approval_before = { buffer = false, file = true, }, }, }, }, }, }, }) ``` ### Never Asking for Certain Commands In Auto mode, a command on the `run_command` safe list runs without asking. The default list is `git status`, `ls` and `pwd`, and your list replaces it: ```lua require("codecompanion").setup({ interactions = { chat = { tools = { ["run_command"] = { opts = { safe_commands = { "git status", "git diff", "ls", "make test" }, }, }, }, }, }, }) ``` An entry covers any arguments after it, so `make test` also covers `make test FILE=tests/test_chat.lua`. A command containing `;`, `&`, `|`, `<`, `>`, `$(`, a backtick or a new line is never safe. > \[!IMPORTANT] > Only add commands whose flags can't write files or run other programs - `git diff --output=notes.txt` writes a file The safe list is ignored in Ask mode. There, press `g1` the first time a command is proposed and it won't be asked about again in that chat. ### Always Asking Before Deleting `delete_file` is *protected* by default, so it asks in both Ask and Auto mode. A protected tool behaves in Auto mode as it does in Ask. To protect another tool, such as `run_command` so that even safe commands ask: ```lua require("codecompanion").setup({ interactions = { chat = { tools = { ["run_command"] = { opts = { protect = true, }, }, }, }, }, }) ``` **YOLO mode ignores `protect`.** ### Letting an LLM Judge Commands In Auto mode, a command that isn't on the safe list asks you. The *judge* sends it to a background LLM first, which runs it if it's judged safe and only asks you if it isn't. The judge's reason is shown in the prompt. To turn it on for `run_command`: ```lua require("codecompanion").setup({ interactions = { chat = { tools = { ["run_command"] = { opts = { judge = true, }, }, }, }, }, }) ``` A command judged safe is remembered for the rest of the chat, as if you'd pressed `g1`. The judge only runs in Auto mode, and never for a protected tool. Use it when you want Auto mode for long tasks but can't predict which commands the LLM needs. See [LLM Judge](/configuration/tools#llm-judge) to choose its adapter and system prompt, and [Background Model](/guides/background-model) for the background interaction it uses. ### Running a Prompt Unattended A [prompt library](/configuration/prompt-library#options) item can start its chat buffer in any mode with `approval_mode`, leaving your other chats in Ask: ::: code-group ```markdown [Markdown] --- name: Fix Failing Tests interaction: chat description: Run the tests and fix whatever fails opts: alias: fixtests approval_mode: yolo tools: - agent --- ## user Run `make test` and fix every failing test. ``` ```lua [Lua] require("codecompanion").setup({ prompt_library = { ["Fix Failing Tests"] = { interaction = "chat", description = "Run the tests and fix whatever fails", opts = { alias = "fixtests", approval_mode = "yolo", -- Can be "ask", "auto" or "yolo" }, tools = { "agent" }, prompts = { { role = "user", content = "Run `make test` and fix every failing test.", }, }, }, }, }) ``` ::: > \[!NOTE] > The older `yolo_mode = true` option still works but starts the chat in Auto, not YOLO ### Resetting a Chat Press `gtx` in the chat buffer to forget everything you've approved in it. This also returns the chat to the default mode in `interactions.chat.tools.opts.approval_mode`. ## Notifications When a tool needs approval and you're in a different buffer, CodeCompanion sends a "Tool approval required" notification. To turn it off: ```lua require("codecompanion").setup({ interactions = { chat = { tools = { opts = { notify_on_approval = false, }, }, }, }, }) ``` ## Agents Agents such as Claude Code and Codex run their own tools and have their own permission systems, so the per-tool options above don't apply to them. Their permission requests appear in the chat buffer, and the approval mode still answers them for you: Auto allows reads, searches, edits and fetches, and checks shell commands against the `run_command` safe list, whilst YOLO allows everything. See [Coding with an Agent](/guides/coding-with-an-agent). ## Limitations * Approvals and the mode are cleared when you close the chat buffer * A safe list entry trusts every flag of that command, so it's only as safe as the commands you add --- --- url: /guides/reviewing-changes.md description: >- Review the edits an LLM or agent has made in Neovim like a pull request, then accept, revert or comment on each one. --- # Reviewing an Agent's Changes An agent can change a dozen files in one turn, and reading them means hunting through your project and copying snippets back into the chat. CodeCompanion's *code review* snapshots your project with git before the agent starts, then shows you everything that's changed since, hunk by hunk, like a pull request. This page walks through the situations you'll review in. [Getting Started](/getting-started#reviewing-the-changes) covers the basics, and [Code Review](/usage/code-review) covers every feature. > \[!IMPORTANT] > Code reviews are in **beta** and need your project to be a git repository ## After a Chat You've asked the LLM to do something in the chat buffer and it's finished. Run: ``` :CodeCompanionCodeReview ``` A new tab opens. The *checklist* on the left lists each changed file and its hunks, and the *review pane* on the right shows the whole file with the changes highlighted. These keymaps are all you need to start: | Keymap | Action | | --- | --- | | `ga` | Accept the hunk, or every hunk in the file when you're on a file row | | `gr` | Revert the hunk, putting that part of the file back as it was | | `gc` | Comment on the line under the cursor | | `u` | Undo the last accept or revert | | `i` / `I` | Edit the real file at this line, in the tab you came from | | `?` | List every keymap | Accepted and reverted hunks leave the checklist. When you want the LLM to act on your comments, go back to the chat and add `#{code_review}` to your message: ```md #{code_review} Please address my comments ``` That closes the *round*. The next time you run `:CodeCompanionCodeReview`, you only see what the LLM changed in response. Clearing every hunk from the checklist and closing the tab also closes the round. ### How Rounds Work The snapshot is taken on the first message you send in a round. If you send more messages before reviewing, the snapshot stays put, so the review covers every edit since you last looked rather than just the latest turn. Because the review is a diff between the snapshot and your files, it catches every change, whether it came from a tool, a shell command the agent ran, or your own typing. Each branch keeps its own snapshot, so switching branches doesn't mix reviews. > \[!NOTE] > Snapshots use a git index owned by CodeCompanion, so your staged changes are never touched ## With an ACP Agent Agents like Claude Code and Codex, used from the chat buffer over [ACP](/agent-client-protocol), work exactly as above. Sending a message takes the snapshot, and `#{code_review}` sends your comments. There's nothing to set up. ## With a CLI Agent How changes are captured depends on where the agent runs. ### In CodeCompanion's CLI If you run Claude Code through the [CLI interaction](/usage/cli), a prompt sent from Neovim, such as `:CodeCompanionCLI `, takes the snapshot. A prompt you type straight into the terminal doesn't, unless you've installed CodeCompanion's hooks: ``` :CodeCompanionCLI Install ``` This adds hooks to `~/.claude/settings.json` that tell Neovim when each turn starts and ends. They only do anything when Claude Code was started by CodeCompanion. See [Hooks](/configuration/cli#hooks) for more. Send your comments with `#{code_review}` in a CLI prompt, just as you would in the chat buffer. ### In a Separate Terminal An agent that CodeCompanion didn't start, such as Claude Code in another terminal, can't tell Neovim when it begins work, so no snapshot is taken for it. For your first review, compare against the start of the branch instead: ``` :CodeCompanionCodeReview Branch ``` Leave your comments, then press `gs`. This writes them to a `review.md` file, copies its path to your clipboard and closes the round. Paste the path into the agent: ``` Please action my code review: /path/to/review.md ``` From then on, `:CodeCompanionCodeReview` only shows what the agent has changed since you shared. > \[!TIP] > The `review.md` path never changes for a repository, so you can reference it in a `CLAUDE.md` or `AGENTS.md` file ## Commenting While You Read You don't need the review window to leave a comment. If you're reading code in any buffer and spot something, put your cursor on the line, or select a range, and run: ``` :CodeCompanionCodeReview Comment ``` The comment shows as virtual text wherever the file is open, and is sent with the rest of your review. Run the same command on the same line to edit it. **Submitting a comment empty deletes it**. ## Before a Pull Request A round only shows what changed since your last review. Before you open a pull request, read the whole branch in one pass, including uncommitted work: ``` :CodeCompanionCodeReview Branch ``` The branch is compared against where it left your default branch, which is taken from `origin/HEAD`, then `init.defaultBranch`, then `main` or `master`. Pending comments are kept. Once you've cleared the last hunk, reviews go back to following rounds. ## Listing Changed Files To see which files were touched: ``` :CodeCompanionChat Changes ``` This opens the quickfix list with every file edited by CodeCompanion's tools or an ACP agent since Neovim started. It doesn't need git, but it doesn't see edits made by a CLI agent. ## Skipping Files Lockfiles and generated files rarely need reading. To leave them out of every review, as if you'd accepted them: ```lua require("codecompanion").setup({ interactions = { code_review = { opts = { auto_accept = { "**/*.lock", "**/package-lock.json", "doc/**/*.txt" }, }, }, }, }) ``` Paths are relative to the repository root and follow `:h vim.glob`. The checklist header shows how many files were auto-accepted. ## Using Your Own Diff Viewer The review always opens in its own tab. If you'd rather read the changes in another plugin, the snapshot is a normal git ref: ``` :DiffviewOpen refs/worktree/codecompanion/baseline :Gitsigns change_base refs/worktree/codecompanion/baseline ``` You can still leave comments with `:CodeCompanionCodeReview Comment`, as long as you're in the working file rather than the baseline side of the diff. See [Configuring Code Reviews](/configuration/code-review) to change the keymaps, comment styling and storage location. ## Limitations * Without git there's nothing to compare against, so `:CodeCompanionCodeReview` has nothing to show. Comments and `:CodeCompanionChat Changes` still work * Files ignored by `.gitignore` never appear in a review. Neither do the contents of an embedded git repository or an uninitialised submodule, which are skipped * The first snapshot in a large repository can pause Neovim while git indexes every file. Later snapshots reuse that index and are much quicker * Comments are stored against line numbers, which drift if the file changes a lot before you send them. The code sent with each comment usually gives the LLM enough context * Closing a round doesn't discard pending comments. Press `gC` in the review window to edit or delete them --- --- url: /guides/web-search.md description: >- Give the LLM in a CodeCompanion chat buffer access to web search, pick the search provider it uses, and fetch specific pages. --- # Setting Up Web Search An LLM only knows what it was trained on, so it can't tell you about a library released last week. The [web\_search](/usage/chat-buffer/agents-tools#web-search) tool lets it search the web from the chat buffer, sending its query to a search provider through an adapter. ## Choosing a Search Provider The default provider is DuckDuckGo, which works without an API key. Tavily and Serply need one, and if it isn't set, every search fails. | Adapter | API key | Notes | |---|---|---| | `duckduckgo` | None | The default. Reads DuckDuckGo's HTML results page. Ignores every search option | | `tavily` | `TAVILY_API_KEY` | Returns relevant chunks from each result | | `serply` | `SERPLY_API_KEY` | Google results. Returns at most 10 per search | To pick one: ::: code-group ```lua [Tavily] -- Reads the TAVILY_API_KEY environment variable require("codecompanion").setup({ interactions = { chat = { tools = { ["web_search"] = { opts = { adapter = "tavily", }, }, }, }, }, }) ``` ```lua [Serply] -- Reads the SERPLY_API_KEY environment variable require("codecompanion").setup({ interactions = { chat = { tools = { ["web_search"] = { opts = { adapter = "serply", }, }, }, }, }, }) ``` ```lua [DuckDuckGo] -- No API key required require("codecompanion").setup({ interactions = { chat = { tools = { ["web_search"] = { opts = { adapter = "duckduckgo", }, }, }, }, }, }) ``` ::: > \[!IMPORTANT] > Don't put `strategies` and `interactions` in the same config. `strategies` is the old name, and when it's present your `interactions` table is replaced, so the tool falls back to DuckDuckGo To read the API key from somewhere else, such as a password manager: ```lua require("codecompanion").setup({ adapters = { http = { extend = { tavily = { env = { api_key = "cmd:op read op://personal/Tavily/credential --no-newline", }, }, }, }, }, }) ``` See [environment variables](/configuration/adapters-http#environment-variables) for the other ways to set a key. ## Searching Add `@{web_search}` to your message: ```md Use @{web_search} to find the latest release of Neovim and summarise what changed ``` The LLM writes the query itself and can limit it to particular sites: ```md Use @{web_search} to search neovim.io and explain how I configure a new language server ``` The search runs without asking for your approval. The results go back to the LLM as a list of titles, URLs and snippets, not full pages. If it needs a whole page, give it [fetch\_webpage](#fetching-a-page) too. > \[!NOTE] > DuckDuckGo ignores the list of sites, so the search covers the whole web ### Search Options The tool passes the options in `opts.opts` to the adapter. The defaults are below, and only apply to Tavily and Serply: ```lua require("codecompanion").setup({ interactions = { chat = { tools = { ["web_search"] = { opts = { adapter = "tavily", opts = { search_depth = "advanced", -- Can be "basic" or "advanced" topic = "general", -- Can be "general" or "news" chunks_per_source = 3, max_results = 5, }, }, }, }, }, }, }) ``` | Option | Adapter | Description | |---|---|---| | `max_results` | Tavily, Serply | Number of results to return | | `topic` | Tavily, Serply | `"news"` searches news only | | `time_range` | Tavily, Serply | Only return results from the last `"day"`, `"week"`, `"month"` or `"year"` | | `search_depth` | Tavily | `"advanced"` returns more relevant chunks, at a higher cost per search | | `chunks_per_source` | Tavily | Number of chunks taken from each result | | `days` | Tavily | How far back a `"news"` search goes (default `7`) | | `gl` | Serply | Country to search from, such as `"gb"` | | `hl` | Serply | Interface language, such as `"en"` | ## Provider Web Search Some providers run web search on their own servers. With these adapters, `@{web_search}` uses the provider's search instead of the tool above, so your search adapter and its API key aren't used: | Adapter | Provider tools | |---|---| | `anthropic` | `web_search`, and `web_fetch` for fetching a page | | `openai` | `web_search` | | `gemini` | `web_search`, using Google Search | | `openrouter` | `web_search`, and `fetch_webpage` in place of the [built-in tool](#fetching-a-page) | Anything the provider charges for search is added to your bill with them. To use the built-in tool instead, turn the provider's tool off: ```lua require("codecompanion").setup({ adapters = { http = { extend = { anthropic = { available_tools = { ["web_search"] = { enabled = false, }, }, }, }, }, }, }) ``` See [adapter tools](/usage/chat-buffer/agents-tools#adapter-tools) for more. ## Fetching a Page When you already know the page you want, there are two ways to add it to the chat: * **`@{fetch_webpage}`** - A tool. The LLM decides which URL to fetch and reads the result straight away * **`/fetch`** - A [slash command](/usage/chat-buffer/slash-commands#fetch). You enter the URL, and can cache the page to add it again later without fetching it ```md Use @{fetch_webpage} to read https://neovim.io/doc/user/lsp.html and tell me how to disable semantic tokens ``` Both use [MarkItDown](https://github.com/microsoft/markitdown) by default. It runs the `markitdown` CLI on your machine, so it must be installed. [Jina](https://jina.ai) is the alternative, which works without an API key and converts the page into plain text. Each one has its own adapter setting: ```lua require("codecompanion").setup({ interactions = { chat = { tools = { ["fetch_webpage"] = { opts = { adapter = "jina", -- Can be "jina" or "markitdown" }, }, }, slash_commands = { ["fetch"] = { opts = { adapter = "jina", -- Can be "jina" or "markitdown" }, }, }, }, }, }) ``` To send a Jina API key with each request: ```lua require("codecompanion").setup({ adapters = { http = { extend = { jina = { env = { api_key = "JINA_API_KEY", }, }, }, }, }, }) ``` ## Limitations * The tools are for HTTP adapters only. Agents like Claude Code bring their own web search * The model you use must support tool use. Smaller local models may not call the tool, or may call it with a malformed query * Only one search adapter can be set at a time, and it's shared by every chat * DuckDuckGo reads a web page meant for people, so it can refuse the request and return "Engine thinks you're a bot", or stop working if the page changes * Search results are snippets. The LLM needs `@{fetch_webpage}` as well to read a result in full --- --- url: /guides/sharing-rules-and-skills.md description: >- Give the LLM your conventions once, with rules files like AGENTS.md and CLAUDE.md and with skills, and have them apply in every project. --- # Sharing Rules and Skills Across Projects An LLM forgets your conventions the moment a chat ends. CodeCompanion gives you two ways to hand them over once: *rules*, which are files added to every new chat, and *skills*, which the LLM only reads in full when a task calls for them. ## Rules Already in Your Project If your project has an `AGENTS.md` or `CLAUDE.md`, there's nothing to configure. Every new chat buffer loads the `default` rule group, which picks up whichever of these files exist: | File | Location | |---|---| | `AGENTS.md`, `AGENT.md` | Current working directory | | `CLAUDE.md`, `CLAUDE.local.md` | Current working directory | | `~/.claude/CLAUDE.md` | Your home directory | | `.github/copilot-instructions.md` | Current working directory | | `.cursorrules`, `.clinerules`, `.windsurfrules`, `.goosehints`, `.rules` | Current working directory | Files that don't exist are skipped. `@path` imports inside `AGENTS.md` and `CLAUDE.md` are followed, so a line like `@docs/STYLE.md` shares that file too. See [Using Rules](/usage/chat-buffer/rules) for how the paths are resolved. Cursor's newer `.cursor/rules` directory isn't in the `default` group. To add it alongside the defaults, create a group for it and autoload both: ```lua require("codecompanion").setup({ rules = { cursor = { description = "Cursor rules", files = { ".cursor/rules/**/*.mdc", }, }, opts = { chat = { autoload = { "default", "cursor" }, }, }, }, }) ``` A glob matches nothing in a project without the directory, so this is safe to leave on everywhere. Cursor's frontmatter, such as `globs` and `alwaysApply`, isn't interpreted, so every matching file is shared in full. ## Personal Rules in Every Project `~/.claude/CLAUDE.md` is already loaded everywhere. If you'd rather keep your own conventions outside of Claude's directory, put them in a group of their own and autoload it after `default`: ```lua require("codecompanion").setup({ rules = { personal = { description = "My conventions for every project", files = { { path = "~/.config/codecompanion/rules", files = "*.md" }, }, }, opts = { chat = { autoload = { "default", "personal" }, }, }, }, }) ``` Groups are loaded in the order they're listed, and a file that appears in more than one group is only added once. `~/.config/codecompanion/rules` has no special meaning to CodeCompanion. It's a directory you create, and any path works. To load a group only in certain projects, make `autoload` a function: ```lua require("codecompanion").setup({ rules = { opts = { chat = { ---@return string|string[] autoload = function() if vim.fn.getcwd():find("work", 1, true) then return { "default", "personal", "work" } end return { "default", "personal" } end, }, }, }, }) ``` Any group can also be added to an open chat with the [/rules](/usage/chat-buffer/slash-commands#rules) slash command, and `gM` removes every rule from the chat. See [Configuring Rules](/configuration/rules) for directories, nested groups and parsers. ## Different Rules for Different Adapters `rules.opts.chat.enabled` can be a function. It's called with the chat when the chat is created, so you can check the adapter: ```lua require("codecompanion").setup({ rules = { opts = { chat = { ---@param chat CodeCompanion.Chat ---@return boolean enabled = function(chat) return chat.adapter.type == "http" end, }, }, }, }) ``` This skips rules entirely for [agents](/configuration/adapters-acp), which is what you want if the agent already reads `CLAUDE.md` or `AGENTS.md` itself. Check `chat.adapter.name`, such as `"claude_code"` or `"codex"`, to target one agent. > \[!NOTE] > `enabled` is only checked when the chat is created. Switching adapter in an open chat doesn't add or remove rules ## Skills A *skill* is a folder with a `SKILL.md` inside, following [Claude's format](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview#how-skills-work). Only its name and description go into the chat. The LLM reads the rest with the `read_file` tool when it decides the skill applies, so a long skill costs almost nothing until it's used. CodeCompanion looks for skills in these directories, and a later one wins if two skills share a name: | Directory | Scope | |---|---| | `~/.config/codecompanion/skills` | Personal | | `.codecompanion/skills` | Project | | `~/.agents/skills` | Personal, shared with other agents | | `~/.claude/skills` | Personal, shared with Claude Code | | `.claude/skills` | Project, shared with Claude Code | So a skill in `~/.claude/skills` is already available, and a project's `.claude/skills` overrides a personal skill of the same name. Each skill is a `SKILL.md` up to five directories below one of these, set by `skills.opts.depth`. Hidden directories are skipped. **The LLM only knows about the skills you add to the chat.** Add them with the [/skills](/usage/chat-buffer/slash-commands#skills) slash command, or have some added to every chat: ```lua require("codecompanion").setup({ skills = { opts = { chat = { autoload = { "lua-developer", "code-review" }, }, }, }, }) ``` `autoload` takes skill names or [group](/configuration/skills#groups) names, and a group can be added in one go with [/skills-group](/usage/chat-buffer/slash-commands#skills-group). Descriptions can span several lines using YAML's `>` folded style: ```markdown --- name: lua-developer description: > The Lua conventions for this project. Use when writing or reviewing Lua. --- ``` See [Configuring Skills](/configuration/skills) for the full set of options and [Creating Skills](/configuration/skills#creating-skills) for how to write one. ## Agents Agents like Claude Code and Codex bring their own way of loading project instructions, so it's worth knowing what CodeCompanion adds on top: * **Rules** - Loaded by default, and sent to the agent as text with your first message. A system prompt taken from a rules file isn't sent, as agents don't take one * **Skills** - Not added. `/skills` and `/skills-group` are hidden, and `autoload` is skipped. Use the agent's own skills through the `\` trigger instead, as covered in [Skills on ACP Adapters](/usage/chat-buffer/skills#acp-adapters) If your agent already reads `CLAUDE.md` or `AGENTS.md`, turn rules off for agents with the [`enabled` function above](#different-rules-for-different-adapters) so the file isn't sent twice. ## Rules, Skills, System Prompts or Prompts | Use | When | Applies to | |---|---|---| | Rules | The LLM should always know it, like coding style or project layout | Every chat, or the chats you add it to | | Skills | The LLM needs it for certain tasks, like writing a migration or a release | Chats you add it to, read only when relevant | | [System prompt](/configuration/system-prompt#changing-system-prompts) | You want to change how the LLM behaves in every chat, whatever the project | Every chat with an HTTP adapter | | [Prompt library](/usage/prompt-library) | You repeat the same request, like writing a commit message | The chat you start from the prompt | A prompt library item can name its own rules and skills, so a prompt like "Review this PR" can always bring your `code-review` skill with it. See [rules in prompts](/configuration/prompt-library) and [skills in prompts](/configuration/skills#prompt-library). ## Limitations * Skills need the Tree-sitter `yaml` parser. Run `:TSInstall yaml` if `/skills` shows nothing * Skills need an HTTP adapter with tool use. They're not added for a model that has tools turned off * A group's own `enabled` function only hides it from `/rules` and the Action Palette. It doesn't stop the group being autoloaded, so use an `autoload` function for that --- --- url: /guides/background-model.md description: >- Run chat titles, compaction and the tool judge on a cheaper model than your main chat. --- # Running Background Tasks on a Cheaper Model Some requests CodeCompanion makes don't need your best model. Naming a chat, summarising a long conversation or judging whether a command is safe to run are short, well-defined tasks that a small, fast model handles well, at a fraction of the cost. ## The Background Interaction The *background* interaction sends requests to an LLM without any input from you. It never streams, and it fails silently, so a broken background request won't interrupt your chat. Three tasks can run on a model of your choosing: * **[Chat titles](#chat-titles)** - Names a chat buffer from its first message. Off by default * **[Tool judge](#tool-judge)** - Decides whether a tool call in Auto mode is safe to run without asking. Off by default * **[Compaction](#compaction)** - Summarises the chat when it nears the context window. On by default Chat titles and the tool judge use the background adapter. **Compaction doesn't.** It uses the chat's own adapter unless you give it one. ## Setting the Background Adapter The background adapter defaults to `copilot`. To point it at a cheaper model: ::: code-group ```lua [Copilot] require("codecompanion").setup({ interactions = { background = { adapter = { name = "copilot", model = "claude-haiku-4.5", }, }, }, }) ``` ```lua [Anthropic] require("codecompanion").setup({ interactions = { background = { adapter = { name = "anthropic", model = "claude-haiku-4-5", }, }, }, }) ``` ```lua [OpenAI] require("codecompanion").setup({ interactions = { background = { adapter = { name = "openai", model = "gpt-5.4-nano", }, }, }, }) ``` ```lua [Gemini] require("codecompanion").setup({ interactions = { background = { adapter = { name = "gemini", model = "gemini-3.1-flash-lite-preview", }, }, }, }) ``` ```lua [Local Model] -- Requires Ollama to be running require("codecompanion").setup({ interactions = { background = { adapter = { name = "ollama", model = "qwen3:4b", }, }, }, }) ``` ::: > \[!IMPORTANT] > The background adapter doesn't follow your chat adapter. If you've switched your chat to another provider and aren't signed in to Copilot, set this too, or your background tasks fail without telling you ## Chat Titles Title generation is off by default. To turn it on: ```lua require("codecompanion").setup({ interactions = { background = { adapter = { name = "anthropic", model = "claude-haiku-4-5", }, chat = { opts = { enabled = true, }, }, }, }, }) ``` The title is used as the buffer name and as the chat's description in the [action palette](/usage/action-palette). A chat that already has a title, such as one restored from a [session](/configuration/sessions), isn't renamed. `chat.opts.enabled` switches on every action in `interactions.background.chat.callbacks`. To give one action its own model, swap its path for a table: ```lua require("codecompanion").setup({ interactions = { background = { chat = { callbacks = { ["on_ready"] = { actions = { { path = "interactions.background.builtin.chat_make_title", adapter = { name = "ollama", model = "qwen3:4b" }, }, }, }, }, opts = { enabled = true, }, }, }, }, }) ``` See [Background Callbacks](/configuration/callbacks#background-callbacks) to attach your own actions. ## Tool Judge The judge vets a tool call in [Auto mode](/usage/chat-buffer/agents-tools#approval-modes) and only asks you when it judges the call unsafe. It uses the background adapter unless you give it one: ```lua require("codecompanion").setup({ interactions = { background = { gates = { judge = { adapter = { name = "openai", model = "gpt-5.4-mini", }, }, }, }, chat = { tools = { ["run_command"] = { opts = { judge = true, }, }, }, }, }, }) ``` The judge is switched on per tool, and doesn't depend on `background.chat.opts.enabled`. If it can't reach a verdict, it asks you. See [LLM Judge](/configuration/tools#llm-judge) for the full set of options. > \[!TIP] > The judge is a safety check, so this is the one task where a slightly stronger small model can be worth the extra cost ## Compaction Compaction summarises the chat once it reaches 85% of the context window. To run the summary on a cheaper model, with a fallback to the chat's adapter if that model fails: ```lua require("codecompanion").setup({ interactions = { chat = { opts = { context_management = { compaction = { adapter = { name = "gemini", model = "gemini-3.5-flash", }, fallback_to_chat_adapter = true, }, }, }, }, }, }) ``` Pick a model with a context window at least as large as your chat model's, as it has to read the conversation it's summarising. See [Compaction](/configuration/context-management#compaction) for the triggers. Compaction never runs for agents, as they manage their own context. It also doesn't run for models that compact on the provider's side, such as some `anthropic` and `openai` models, unless you [disable server-side compaction](/configuration/adapters-http#disabling-compaction). ## Other Interactions The inline and cmd interactions each have their own adapter, so you can give them a cheaper model too: ```lua require("codecompanion").setup({ interactions = { inline = { adapter = { name = "anthropic", model = "claude-haiku-4-5", }, }, cmd = { adapter = { name = "anthropic", model = "claude-haiku-4-5", }, }, }, }) ``` A [prompt library](/configuration/prompt-library) item can also set `opts.adapter`, so a prompt that only writes a commit message doesn't have to use your chat model. ## Limitations * Only HTTP adapters can be background adapters. Setting an agent, such as `claude_code`, logs a warning and the task doesn't run * Background failures are only written to the [log](/troubleshooting). If titles stop appearing, check the log first * Chat titles and the judge ask for a structured response. A model that can't return one still produces titles, but the judge asks you every time * The `/compact` slash command always uses the chat's adapter, ignoring `compaction.adapter` --- --- url: /usage/action-palette.md description: Open chats, sessions, prompts and workflows from a single menu in Neovim. --- # Using the Action Palette The *Action Palette* is a single menu for starting chats, switching between them and running prompts from your [prompt library](/usage/prompt-library). To open it: ``` :CodeCompanionActions ``` Opened from a chat buffer, it lists that chat's keymaps and slash commands instead. ## Actions The palette starts with these actions: | Action | Description | | --- | --- | | `Chat` | Open a new chat buffer | | `Open chats ...` | Move to any open chat buffer | | `Saved sessions ...` | Restore a chat [saved to disk](/configuration/sessions) | | `Chat with rules ...` | Open a chat buffer with a [rule group](/usage/chat-buffer/rules) loaded | To hide them, set `display.action_palette.opts.show_preset_actions = false`. ## Built-in Prompts Below the actions are the prompts that ship with CodeCompanion: | Prompt | Alias | Description | | --- | --- | --- | | `Commit message` | `commit` | Generate a commit message | | `Explain code` | `explain` | Explain how code in a buffer works | | `Explain LSP diagnostics` | `lsp` | Explain the LSP diagnostics for the selected code | | `Fix code` | `fix` | Fix the selected code | | `Help with CodeCompanion` | `help` | Answer a question from CodeCompanion's documentation | | `Inline prompt` | | Prompt the LLM from inside a Neovim buffer | | `Unit tests` | `tests` | Generate unit tests for the selected code | | `Code workflow` | | Run the built-in [workflow](/usage/workflows) | Prompts with an alias can also be run from the command line: ``` :CodeCompanion /explain ``` To hide the built-in prompts, set `display.action_palette.opts.show_preset_prompts = false`. ## Refreshing The palette caches its items. After editing your prompt library, reload them with: ``` :CodeCompanionActions Refresh ``` --- --- url: /usage/chat-buffer.md description: >- Converse with an LLM or an agent in a Neovim buffer, sharing context and switching adapters as you go. --- # Using the Chat Buffer The *chat buffer* is where you converse with an LLM or an agent. It's a markdown buffer, with a filetype of `codecompanion`, where `H2` headers separate your messages from the LLM's responses. To open a chat buffer: ``` :CodeCompanionChat ``` To show or hide it: ``` :CodeCompanionChat Toggle ``` The Lua equivalents, `require("codecompanion").chat()` and `require("codecompanion").toggle()`, also accept window options: ```lua require("codecompanion").chat({ window_opts = { layout = "float", width = 0.6 } }) ``` Press `` or `` in normal mode, or `` in insert mode, to send your message. The response streams back into the buffer. Whilst the LLM is responding, press `gm` to send it a follow-up, which CodeCompanion delivers once it's safe to do so. To write your messages in a separate split instead, see [Customising the Chat Experience](/guides/customising-the-chat-experience#writing-your-message). ## Action Palette From a chat buffer, `:CodeCompanionActions` opens the chat's own [Action Palette](/usage/action-palette), listing its keymaps and slash commands. ## Changing Adapter and Model Press `ga` to pick a different adapter. If the adapter has more than one model, you're asked to pick one of those too. This works for both HTTP and ACP adapters, so you can move between an LLM and an agent in the same chat. ## Changing ACP Command ACP adapters start the agent with a command from your configuration, `default` unless you choose otherwise. To start one with a different command: ``` :CodeCompanionChat adapter=gemini_cli command=yolo ``` From inside a chat buffer, use the [/command](/usage/chat-buffer/slash-commands#command) slash command. ## Completion Type a trigger character to open the completion menu: | Trigger | Completes | | --- | --- | | `#` | [Editor context](/usage/chat-buffer/editor-context), such as `#{buffer}` | | `@` | [Tools](/usage/chat-buffer/agents-tools), such as `@{agent}` | | `/` | [Slash commands](/usage/chat-buffer/slash-commands) | | `\` | Commands from an ACP agent, such as `\compact` | Completion works with blink.cmp, nvim-cmp and coc.nvim. Without a completion plugin, press `` for native completion. The trigger characters can be changed under `opts.triggers`. ACP commands come from the agent itself, and CodeCompanion turns `\command` into `/command` when you send the message. The backslash keeps them apart from CodeCompanion's own slash commands. > \[!NOTE] > ACP commands take a few seconds to appear after you open a chat buffer, and only the commands an agent exposes through its SDK are listed ## Context Add context with [editor context](/usage/chat-buffer/editor-context), [slash commands](/usage/chat-buffer/slash-commands) and [tools](/usage/chat-buffer/agents-tools). Each item is listed in a `Context` blockquote at the top of your message. > \[!IMPORTANT] > A context item is a snapshot of its source at the time it was added. Only `#{buffer}` and `#{buffers}` stay in sync by default To keep any other buffer or file up to date, sync it to the chat buffer. Put your cursor on the context item and press: * `gba` to send its entire content on every turn * `gbd` to send only what's changed since the last turn Sending the entire content means the LLM always sees the latest version, at the cost of more tokens. Sending the diff is cheaper. Some file types are worth syncing as soon as they're added. Jupyter Notebooks change on disk every time a cell runs, so they're synced by default. To add more, see [Syncing](/configuration/chat-buffer#syncing). HTTP adapters send the whole conversation on every turn, so the LLM can refer to context you shared many messages ago. To change that history, use the [debug window](#debug-window). ### Adding via Paths To attach a file or URL, write it as a [markdown link](https://www.markdownguide.org/basic-syntax/#links): ```markdown I want to share [File](~/Code/Neovim/codecompanion.nvim/README.md) with you ``` When you send the message, the file is attached and the link is replaced with its path. This works for text files, images and PDFs. URLs are fetched with the [fetch](/usage/chat-buffer/slash-commands#fetch) slash command's adapter, without a cache. Markdown can't parse a bare space in a link, so wrap a path that contains one: | Link | Attached | | --- | --- | | `[File](/Users/Oli/Downloads/report.txt)` | Yes | | `[File]()` | Yes | | `[File]('/Users/Oli/Downloads/some report.txt')` | Yes | | `[File]("/Users/Oli/Downloads/some report.txt")` | Yes | | `[File](/Users/Oli/Downloads/some report.txt)` | No | ### Removing To remove a context item, delete its line from the `Context` blockquote. On the next turn, everything it added is removed from the message history. ## Debug Window Press `gd` to open the *debug window*. It shows what's sent to the LLM on the next turn: the adapter's settings, the context items and the full message history, including the system prompt and other messages hidden from the chat buffer. The debug window is a Lua buffer. Edit it, then press `` to write your changes back to the chat buffer. ## Generating Titles CodeCompanion can give each chat buffer a title, written by a [background interaction](/guides/background-model). To turn it on: ```lua require("codecompanion").setup({ interactions = { background = { chat = { opts = { enabled = true, }, }, }, }, }) ``` See [Chat Titles](/guides/background-model#chat-titles) to choose the model that writes them. ## Images Add images from disk with [/file](/usage/chat-buffer/slash-commands#file), from a URL with [/file-from-url](/usage/chat-buffer/slash-commands#file-from-url), or from the clipboard with [img-clip.nvim](/installation#img-clip-nvim). Both local and remote images are base64 encoded. If the model doesn't accept images, CodeCompanion leaves them out of the request. ## Keymaps Press `?` in a chat buffer to list every keymap. The defaults in normal mode are: | Keymap | Name | Description | | --- | --- | --- | | `` `` | `send` | Send the message to the LLM | | `` | `close` | Close the chat buffer | | `q` | `stop` | Stop the current request | | `ga` | `change_adapter` | Change adapter and model | | `gba` | `sync_all` | Toggle live-syncing of a context item | | `gbd` | `sync_diff` | Toggle diff-only syncing of a context item | | `gc` | `codeblock` | Insert an empty codeblock | | `gd` | `debug` | Open the debug window | | `gf` | `fold_code` | Fold all codeblocks | | `gm` | `_btw` | Send a follow-up whilst the LLM is responding | | `gM` | `rules` | Remove rules from the chat | | `gr` | `regenerate` | Regenerate the last response | | `gR` | `goto_file_under_cursor` | Open the file path under the cursor | | `gs` | `system_prompt` | Toggle the system prompt on and off | | `gS` | `copilot_stats` | Show Copilot usage statistics | | `gtx` | `clear_approvals` | Reset cached tool approvals | | `gty` | `yolo_mode` | Choose how tool calls are approved | | `gx` | `clear` | Clear all messages from the chat | | `gy` | `yank_code` | Yank code from the last codeblock | | `K` | `tool_call_details` | Show the tool call under the cursor | | `}` `{` | `next_chat` `previous_chat` | Move to the next or previous chat | | `]]` `[[` | `next_header` `previous_header` | Jump to the next or previous header | In insert mode, `` sends the message, `` closes the chat buffer and `` opens the completion menu. To change any of them, see [Keymaps](/configuration/chat-buffer#keymaps). ## Multiple Chats Open as many chat buffers as you like. Cycle through them with `{` and `}`, and use `:CodeCompanionChat Toggle` to show or hide the last one. By default, opening or cycling to a chat hides the one that's visible. To give each tab its own chat, so a chat in one tab is never closed or replaced from another: ```lua require("codecompanion").setup({ display = { chat = { window = { pertab = true, }, }, }, }) ``` With `pertab` enabled, `{` and `}` only cycle through chats that are visible in the current tab or hidden everywhere, and `:CodeCompanionChat Toggle` jumps to the tab a chat lives in. ## Settings To tweak the model's settings between responses, show them as a YAML block at the top of the chat buffer: ```lua require("codecompanion").setup({ display = { chat = { show_settings = true, }, }, }) ``` The block mirrors the adapter's `schema` table. Edit a value and it's used from the next response onwards. --- --- url: /usage/chat-buffer/agents-tools.md description: >- Let an LLM edit files, run commands and search the web from the chat buffer, with tool groups, approvals and approval modes. --- # Using Agents and Tools > \[!IMPORTANT] > The built-in tools are for HTTP adapters only, and not every LLM supports tool use. See [compatibility](#compatibility) An LLM acts as an agent when it can use tools, such as searching the web or running code ([Agentic Design Patterns Part 3, Tool Use](https://www.deeplearning.ai/the-batch/agentic-design-patterns-part-3-tool-use)). In CodeCompanion, a tool is context and an action shared with the LLM, which the chat buffer runs inside Neovim on the LLM's behalf. Add a tool to the chat buffer by typing `@`. > \[!IMPORTANT] > Some tools need your approval before they run, making you the human in the loop ## How They Work Tools use an LLM's [function calling](https://platform.openai.com/docs/guides/function-calling) ability. Every tool in CodeCompanion follows [OpenAI's specification for defining functions](https://platform.openai.com/docs/guides/function-calling#defining-functions). For HTTP adapters, CodeCompanion is the *harness*: the system prompt, tools, approvals and [context management](/architecture#how-context-is-managed) that turn an LLM into an agent. ACP adapters such as Claude Code and Codex bring their own harness, which is why the built-in tools are for HTTP adapters only. The harness runs the *agent loop*: 1. You submit a prompt 2. The LLM responds, optionally asking to run one or more tools 3. CodeCompanion runs each tool in turn, asking for your [approval](#approvals) where needed 4. The tools' output is sent back to the LLM and the loop returns to step 2 The loop ends when the LLM responds without asking for a tool, or when you stop the request or cancel a tool. Rejecting a tool doesn't end the loop. The LLM is told you rejected it, along with your reason, and carries on. Each tool call is summarised in the chat buffer. Press `K` on a line to see the tool call in detail. Press `K` again to move into the tool call. For ACP adapters, an agent that sends no arguments shows the tool call's title and locations instead. The [tool system architecture](/extending/tools#architecture) is outlined in the extending section. ## Tool Groups A *tool group* makes several tools available to the LLM with a single `@{group_name}` reference. CodeCompanion comes with two: `@{agent}` and `@{files}`. By default, a group shows as a single `name` context item in the chat buffer. To list each of its tools as a context item instead, set `opts.collapse_tools = false` on the group. A group's `prompt` replaces its reference in your message, so the LLM receives a sentence rather than the group's name. `${tools}` in the prompt expands to the group's tools. ### agent The `@{agent}` group is CodeCompanion's agent mode. It has its own system prompt, which replaces the default chat and tool system prompts, and contains: * [ask\_questions](#ask-questions) * [create\_file](#create-file) * [delete\_file](#delete-file) * [edit\_file](#edit-file) * [file\_search](#file-search) * [get\_changed\_files](#get-changed-files) * [get\_diagnostics](#get-diagnostics) * [grep\_search](#grep-search) * [overwrite\_file](#overwrite-file) * [read\_file](#read-file) * [run\_command](#run-command) To use it: ```md @{agent} Can we create a todo list app in Vue.js? ``` ### files The `@{files}` group carries out file operations in the current working directory. It contains: * [create\_file](#create-file) * [delete\_file](#delete-file) * [edit\_file](#edit-file) * [file\_search](#file-search) * [get\_changed\_files](#get-changed-files) * [grep\_search](#grep-search) * [overwrite\_file](#overwrite-file) * [read\_file](#read-file) To use it: ```md @{files} Can you scaffold out the folder structure for a python package? ``` ### Custom Agents A group becomes an agent when it has its own `system_prompt`. With `ignore_system_prompt` and `ignore_tool_system_prompt`, it replaces the default system prompts entirely. This is how `@{agent}` works. A `system_prompt` function receives the group's config and a [context object](/configuration/system-prompt) with `language`, `cwd`, `date`, `nvim_version`, `os` and more: ```lua require("codecompanion").setup({ interactions = { chat = { tools = { groups = { ["my_agent"] = { description = "My custom agent", system_prompt = function(group, ctx) return string.format("You are a coding agent. The date is %s. The user is on %s.", ctx.date, ctx.os) end, tools = { "read_file", "edit_file", "run_command" }, opts = { collapse_tools = true, ignore_system_prompt = true, ignore_tool_system_prompt = true, }, }, }, }, }, }, }) ``` ## Built-in Tools The built-in tools work with any adapter and model that [supports tool use](#compatibility): | Tool | Description | | --- | --- | | [ask\_questions](#ask-questions) | Ask you clarifying questions before acting | | [create\_file](#create-file) | Create a file | | [delete\_file](#delete-file) | Delete a file in the current working directory | | [edit\_file](#edit-file) | Edit a buffer or file by replacing exact text | | [fetch\_webpage](#fetch-webpage) | Fetch the content of a webpage | | [file\_search](#file-search) | Find files by glob pattern | | [get\_changed\_files](#get-changed-files) | Get git diffs of the current changes | | [get\_diagnostics](#get-diagnostics) | Get LSP diagnostics for a file | | [grep\_search](#grep-search) | Search for text in files | | [memory](#memory) | Store and retrieve information across conversations | | [overwrite\_file](#overwrite-file) | Replace the whole content of a buffer or file | | [read\_file](#read-file) | Read all or part of a file | | [run\_command](#run-command) | Run shell commands | | [search\_help](#search-help) | Search the CodeCompanion docs | | [web\_search](#web-search) | Search the web | When you send a prompt, each tool reference is replaced with `interactions.chat.tools.opts.tool_replacement_message`, which defaults to `"the ${tool} tool"`. So this: ```md Use @{grep_search} to find where the adapter is resolved ``` reaches the LLM as: ```md Use the grep_search tool to find where the adapter is resolved ``` ### ask\_questions > \[!NOTE] > This tool is hidden from the completion menu and comes with the `@{agent}` group The LLM asks you up to four clarifying questions before acting. It uses this when requirements are ambiguous, when it needs to choose between approaches or when it wants to check an assumption: ```md @{agent} Can you refactor the authentication module? ``` ### create\_file > \[!NOTE] > By default, you see a preview of the file's contents and confirm it before the file is created ```md Can you create some test fixtures using @{create_file}? ``` | Option | Default | Description | | --- | --- | --- | | `require_approval_before` | `false` | Require approval before showing the preview | | `require_confirmation_after` | `true` | Show a preview of the contents and require confirmation before creating the file | ### delete\_file > \[!NOTE] > By default, this tool requires your approval before it runs ```md Can you use @{delete_file} to delete the quotes.lua file? ``` | Option | Default | Description | | --- | --- | --- | | `judge` | `false` | Let the [LLM judge](/configuration/tools#llm-judge) decide in Auto mode | | `protect` | `true` | Always ask before deleting a file in Auto mode | | `require_approval_before` | `true` | Require approval before deleting a file | ### edit\_file > \[!NOTE] > By default, you confirm each edit in a diff before it's written The LLM edits buffers and files by replacing an exact piece of text with new text: ```md Use @{edit_file} to refactor the code in #buffer ``` The text being replaced must match the file exactly, including indentation. If it can't be found, or it appears more than once, the edit fails and the LLM is told why so it can try again. A file that's open in Neovim is edited in its buffer and saved, and any other file keeps its line endings. | Option | Default | Description | | --- | --- | --- | | `require_approval_before.buffer` | `false` | Require approval before editing a buffer | | `require_approval_before.file` | `false` | Require approval before editing a file | | `require_confirmation_after` | `true` | Require confirmation of the diff before the edit is written | | `file_size_limit_mb` | `2` | Files larger than this aren't edited | ### fetch\_webpage The LLM fetches the content of a webpage, converted to text by the tool's adapter: ```md Use @{fetch_webpage} to tell me what the latest version on neovim.io is ``` | Option | Default | Description | | --- | --- | --- | | `adapter` | `"markitdown"` | The adapter that fetches and converts the page. Can be `"markitdown"` or `"jina"` | ### file\_search The LLM finds files in the current working directory by glob pattern, and receives the matching paths: ```md Use @{file_search} to list all the lua files in my project ``` | Option | Default | Description | | --- | --- | --- | | `max_results` | `500` | Maximum number of paths sent to the LLM | ### get\_changed\_files The LLM gets git diffs of the changes in the current working directory, covering staged, unstaged and merge conflicted files: ```md Use @{get_changed_files} to see what's changed ``` | Option | Default | Description | | --- | --- | --- | | `max_lines` | `1000` | Maximum number of diff lines sent to the LLM | ### get\_diagnostics > \[!WARNING] > This tool relies on language servers, so it may be unreliable for some filetypes The LLM gets the LSP diagnostics for a file (errors, warnings, information and hints) along with the lines they refer to: ```md Use @{get_diagnostics} to check for any issues in the current file ``` The LLM can pass a minimum `severity` of `ERROR`, `WARNING`, `INFORMATION` or `HINT`. It defaults to `HINT`, which includes everything. ### grep\_search > \[!IMPORTANT] > This tool requires [ripgrep](https://github.com/BurntSushi/ripgrep) and is unavailable without it The LLM searches for text in files in the current working directory, and receives the path and line number of each match: ```md Use @{grep_search} to find all occurrences of `buf_add_message` ``` | Option | Default | Description | | --- | --- | --- | | `max_results` | `100` | Maximum number of matches sent to the LLM | | `require_approval_before` | `true` | Require approval before searching | | `respect_gitignore` | `true` | Skip files ignored by git | ### memory > \[!IMPORTANT] > Every memory operation is restricted to the `/memories` directory and any whitelisted paths The LLM stores and retrieves information across conversations in a memory directory, `/memories`. With the Anthropic adapter, this tool is the client side of Anthropic's [memory tool](https://docs.claude.com/en/docs/agents-and-tools/tool-use/memory-tool). ```md Use @{memory} to carry on our conversation about streamlining my dotfiles ``` The LLM can use these commands: | Command | Description | | --- | --- | | `view` | List a directory, two levels deep, or show a file with an optional line range | | `create` | Create a file, or overwrite an existing one | | `str_replace` | Replace text in a file, which must match exactly once | | `insert` | Insert text at a line number | | `delete` | Delete a file, or a directory and everything in it | | `rename` | Move or rename a file or directory | By default, the tool requires your approval before it runs (`require_approval_before = true`). **Whitelisted Paths** To give the LLM access to paths outside `/memories`, map each one to a virtual prefix: ```lua require("codecompanion").setup({ interactions = { chat = { tools = { ["memory"] = { opts = { whitelist = { { path = "~/.dotfiles", as = "/dotfiles" }, { path = "/shared/notes", as = "/notes" }, }, }, }, }, }, }, }) ``` The LLM then uses `/dotfiles/.zshrc` or `/notes/todo.md` just as it uses `/memories/file.txt`. A `~` is expanded, and directory traversal protection applies to every whitelisted path. A single file can be mounted too, such as a personal profile the LLM learns from and updates over time: ```lua require("codecompanion").setup({ interactions = { chat = { tools = { ["memory"] = { opts = { whitelist = { { path = "~/.dotfiles/PERSONAL.md", as = "/personal" }, }, }, }, }, }, }, }) ``` ### overwrite\_file > \[!NOTE] > By default, you confirm the change in a diff before it's written The LLM replaces the whole content of an existing buffer or file. It suits rewrites, where [edit\_file](#edit-file) would need many small replacements: ```md Use @{overwrite_file} to rewrite #buffer as a Lua module ``` The file must already exist. A file that's open in Neovim is overwritten in its buffer and saved, and any other file keeps its CRLF line endings. | Option | Default | Description | | --- | --- | --- | | `require_approval_before` | `false` | Require approval before showing the diff | | `require_confirmation_after` | `true` | Require confirmation of the diff before the file is overwritten | | `file_size_limit_mb` | `2` | Files larger than this aren't overwritten | ### read\_file The LLM reads all or part of a file, using an absolute path or one relative to the current working directory. This gives it context from files you haven't shared: ```md Use @{read_file} to read the README and summarise the project ``` | Option | Default | Description | | --- | --- | --- | | `require_approval_before` | `true` | Require approval before reading a file | ### run\_command The LLM runs shell commands on your machine: ```md Can you use @{run_command} to run my test suite with `pytest`? ``` If a command succeeds without writing to [stdout](https://en.wikipedia.org/wiki/Standard_streams#Standard_output_\(stdout\)), the LLM is told there was no output. If it fails, the LLM receives its stderr and stdout. The LLM is instructed to flag a command that runs a test suite. CodeCompanion then records whether the tests passed as a flag on the chat buffer, which [agentic workflows](/extending/agentic-workflows) can hook into. | Option | Default | Description | | --- | --- | --- | | `judge` | `false` | Let the [LLM judge](/configuration/tools#llm-judge) decide in Auto mode | | `require_approval_before` | `true` | Require approval before running a command | | `safe_commands` | `{ "git status", "ls", "pwd" }` | Commands that run without asking in Auto mode | | `timeout` | `300000` | Milliseconds before a command is terminated | ### search\_help The LLM searches the CodeCompanion docs, grounding its answers in the plugin's documentation rather than its own, possibly out of date, knowledge: ```md Use @{search_help} to find out how I can do a code review in CodeCompanion ``` ### web\_search The LLM searches the web, so it can answer with up to date information: ```md Use @{web_search} to search neovim.io and explain how I can configure a new language server ``` By default, the tool uses DuckDuckGo, which needs no API key. Other search providers can be [configured](/configuration/tools#web-search). ## Adapter Tools > \[!NOTE] > Adapter tools are configured in the `available_tools` table on the adapter *Adapter tools* are owned by LLM providers and run remotely, covering tasks such as web search and code execution. They were added in [v17.30.0](https://github.com/olimorris/codecompanion.nvim/releases/tag/v17.30.0). You use them in the same way as the built-in tools, and **an adapter tool takes precedence over a built-in tool of the same name**. | Adapter | Tool | Description | | --- | --- | --- | | `anthropic` | `code_execution` | Run Bash commands and manipulate files in a sandbox | | `anthropic` | `memory` | Store and retrieve information across conversations, using the [memory](#memory) tool as its client | | `anthropic` | `web_fetch` | Retrieve the full content of webpages and PDF documents | | `anthropic` | `web_search` | Search the web for up to date information | | `gemini` | `web_search` | Search the web with Google Search | | `openai` | `web_search` | Search the web for up to date information | | `openrouter` | `fetch_webpage` | Fetch the content of a URL, with any model | | `openrouter` | `web_search` | Search the web, with any model | ## MCP The tools from your [configured](/configuration/mcp) MCP servers are available in the chat buffer once a server has started. Type `@` to find them in the completion menu, prefixed with `mcp:`. ## Security Tools are designed to keep the LLM from making changes that are hard to [recover from](https://www.businessinsider.com/replit-ceo-apologizes-ai-coding-tool-delete-company-database-2025-7). `delete_file` refuses any path outside the current working directory, and `memory` is restricted to its own directory and any whitelisted paths. ### Approvals > \[!NOTE] > This applies to CodeCompanion's built-in tools only. ACP agents have their own tools and approval systems Approvals are kept per chat buffer and per tool. Approving a tool in one chat buffer doesn't approve it anywhere else, and approving it once means you're asked again next time. The request shows the tool call's summary line, the same one you see once it runs. Press `gv`, or `K` on that line, to see the full call in a floating window. When asked, you choose from: | Keymap | Choice | Description | | --- | --- | --- | | `gv` | View | Show the tool call's arguments in a floating window | | `g1` | Always accept | Run this tool without asking again in this chat buffer | | `g2` | Accept | Run this tool this one time | | `g3` | Reject | Don't run the tool, and give the LLM a reason | | `g4` | Cancel | Cancel this tool and every pending tool | Tools with `require_cmd_approval = true`, such as `run_command` and `delete_file`, are approved per command rather than per tool. If you always accept `make format`, you're still asked before `make test`. Press `gtx` to reset the approvals for the chat buffer. ### Approval Modes Press `gty` in the chat buffer to choose how tools are approved: | Mode | Description | | --- | --- | | Ask | Approve each tool before it runs | | Auto | Tools run without asking, apart from protected tools and commands that aren't on your safe list | | YOLO | Everything runs without asking | Every chat buffer starts in Ask. To start in a different mode: ```lua require("codecompanion").setup({ interactions = { chat = { tools = { opts = { approval_mode = "auto", -- Can be "ask", "auto" or "yolo" }, }, }, }, }) ``` Resetting the approvals with `gtx` returns the chat buffer to this mode. In Auto mode, a protected tool always asks first. `delete_file` is protected by default: ```lua require("codecompanion").setup({ interactions = { chat = { tools = { ["delete_file"] = { opts = { protect = true, }, }, }, }, }, }) ``` In Auto mode, `run_command` asks first unless the command is on its safe list: ```lua require("codecompanion").setup({ interactions = { chat = { tools = { ["run_command"] = { opts = { safe_commands = { "git status", "git diff", "make test" }, }, }, }, }, }, }) ``` A command is safe if it starts with an entry on the list, so `git status` also covers `git status --short`. **A command that chains, nests or redirects, with `;`, `&`, `|`, `>`, `<`, `$(`, a backtick or a new line, is never treated as safe.** > \[!IMPORTANT] > An entry covers every flag the command accepts. Only add commands whose flags can't write files or run other programs: `git diff --output=notes.txt` writes a file and `rg --pre` runs one If you've enabled the [LLM judge](/configuration/tools#llm-judge), it decides on commands that aren't on the safe list instead of asking you. A [prompt library](/configuration/prompt-library#options) item can start its chat buffer in a given mode with `opts.approval_mode = "auto"`. Approval modes also apply to ACP agents. In Auto mode, reads, searches, edits and fetches are approved, and everything else asks. Any shell command is checked against the `run_command` safe list, even when the agent labels it as a read or a search. > \[!WARNING] > YOLO mode runs every tool, including protected ones, without asking. Only use it where you can recover from lost data, as you're responsible for any damage it causes ## Compatibility Tool use by adapter: | Adapter | Supported | Notes | | --- | :---: | --- | | Anthropic | :white\_check\_mark: | Dependent on the model | | Azure OpenAI | :white\_check\_mark: | Dependent on the model | | Copilot | :white\_check\_mark: | Dependent on the model | | DeepSeek | :white\_check\_mark: | Dependent on the model | | Gemini | :white\_check\_mark: | Dependent on the model | | Gemini (Legacy) | :white\_check\_mark: | Dependent on the model | | Hugging Face | :white\_check\_mark: | Dependent on the model | | Kimi | :white\_check\_mark: | Dependent on the model | | Mistral | :white\_check\_mark: | Dependent on the model | | Novita | :white\_check\_mark: | Dependent on the model | | Ollama | :white\_check\_mark: | Dependent on the model. Tested with Qwen3 | | OpenAI | :white\_check\_mark: | Dependent on the model | | OpenAI (Legacy) | :white\_check\_mark: | Dependent on the model | | OpenRouter | :white\_check\_mark: | Dependent on the model | | xAI | :x: | Not supported yet | --- --- url: /usage/chat-buffer/editor-context.md description: >- Share buffers, diagnostics, git diffs, terminal output and other Neovim state with an LLM from the chat buffer. --- # Using Editor Context Editor context shares the state of Neovim with an LLM. Add `#{name}` to a message in the chat buffer, such as `#{buffer}`, and CodeCompanion adds the content when you send it. Type `#` to list everything available through completion. | Editor context | Shares | | --- | --- | | `#{buffer}` | A buffer, kept in sync with the chat | | `#{buffers}` | Every open buffer, kept in sync with the chat | | `#{code_review}` | Your pending code review comments | | `#{diagnostics}` | The diagnostics in a buffer, with the code they point at | | `#{diff}` | The staged and unstaged git diff | | `#{messages}` | Neovim's message history | | `#{quickfix}` | The files in the quickfix list | | `#{selection}` | Your current or most recent visual selection | | `#{terminal}` | The latest output from a terminal buffer | | `#{viewport}` | The code visible in your windows | > \[!IMPORTANT] > Apart from `#{buffer}` and `#{buffers}`, editor context is a snapshot taken when you send the message. To share the latest state, use it again in a new message To add your own, see [Editor Context](/configuration/chat-buffer#editor-context). ## #buffer `#{buffer}` shares the last buffer you were in. To share a different open buffer, add its name after a colon: | Syntax | Shares | | --- | --- | | `#{buffer}` | The last buffer you were in | | `#{buffer:init.lua}` | The open buffer with this file name | | `#{buffer:src/main.rs}` | The open buffer at this path | A path can be absolute, relative to the current working directory, or the file's parent directory and name. Only open buffers are matched. If none match, nothing is shared and a warning is logged. To compare two buffers: ```md Compare #{buffer:old_file.js} with #{buffer:new_file.js} and explain the differences. ``` > \[!TIP] > To pick several buffers at once, use the [/buffer](/usage/chat-buffer/slash-commands#buffer) slash command ### Syncing A shared buffer is *synced* with the chat, so the LLM sees your edits on later turns. A parameter after the editor context sets what's sent: | Parameter | Sends | | --- | --- | | `{diff}` | Only what's changed since the last turn. The default | | `{all}` | The whole buffer on every turn | `{diff}` uses fewer tokens on large files. `{all}` always gives the LLM a complete, up-to-date copy. Parameters work with a named buffer too: ```md #{buffer}{all} #{buffer:config.lua}{all} ``` Use `gba` and `gbd` on an item in the [Context](/usage/chat-buffer/#context) blockquote to toggle syncing after it's shared. To change the default parameter, see [Syncing](/configuration/chat-buffer#syncing). ## #buffers `#{buffers}` shares every open buffer, synced in the same way as [#buffer](#syncing) and with the same `{diff}` and `{all}` parameters: ```md #{buffers} can you explain what's going on in these files? ``` Buffers with the `nofile`, `quickfix`, `prompt` or `popup` buftype, and the `codecompanion`, `help` or `terminal` filetype, are skipped. These lists live in `interactions.shared.editor_context.opts.excluded`. ## #code\_review `#{code_review}` shares your [code review](/usage/code-review). Every pending comment you've left with `:CodeCompanionCodeReview Comment` is sent, and the round closes off so the next review only shows what changes next: ```md Please action #{code_review} ``` Each comment reaches the LLM with the file, the line range and the code you commented on. The chat buffer shows a shorter version without the code, so you can scroll back through earlier rounds and see what you asked for. > \[!NOTE] > Sharing your review clears the pending comments and their virtual text. Like a pull request review, it also approves everything you didn't comment on ## #diagnostics `#{diagnostics}` shares every diagnostic in the last buffer you were in, from LSP servers or any other source, along with the lines of code each one points at: ```md #{diagnostics} can you explain the LSP errors in this file and how to fix them? ``` To share another open buffer's diagnostics, name it in the same way as [#buffer](#buffer): `#{diagnostics:init.lua}`. If no buffer matches, the last buffer you were in is used. > \[!TIP] > The [Action Palette](/usage/action-palette) has a prompt that asks an LLM to explain the diagnostics in a visual selection ## #diff `#{diff}` shares the git diff of the current working directory, including staged and unstaged changes. Use it to ask for a commit message or feedback on your recent changes: ```md Sharing the latest git diff with you #{diff} ``` ## #messages `#{messages}` shares Neovim's message history, as shown by `:messages`. Use it when an error has been written there: ```md Can you explain the error I've just observed in Neovim? #{messages} ``` ## #quickfix `#{quickfix}` shares the files in the quickfix list, such as compiler errors, search results or diagnostics across several files: ```md The relevant output from my quickfix list has now been shared with you #{quickfix} ``` Files under 100 lines are shared in full, with their entries listed above. In larger files, entries are grouped by the Tree-sitter symbol they sit in, and only that code is shared. An entry that points at a file rather than a line shares the whole file. ## #selection `#{selection}` shares your current or most recent visual selection, so you can ask about a piece of code without sharing the whole buffer. The selection is captured when you open or toggle a chat buffer: ```md Sharing the relevant code with you #{selection} ``` ## #terminal `#{terminal}` shares the output from the last terminal buffer you entered Terminal mode in. Later uses only share the output added since the last time. Use it for test results, build output or command-line errors: ```md This was the output in my terminal #{terminal} ``` ## #viewport `#{viewport}` shares the code visible in your windows when you send the message. The chat buffer, and any buffer [#buffers](#buffers) skips, is left out: ```md Sharing what I can see in Neovim #{viewport} ``` --- --- url: /usage/chat-buffer/rules.md description: >- Add rules files such as AGENTS.md, CLAUDE.md and Cursor rules to the chat buffer, giving an LLM persistent instructions and project context. --- # Using Rules LLMs don't remember anything between chats, so your instructions and project context have to be shared again each time. *Rules* are the files that hold them, such as `AGENTS.md` or `CLAUDE.md`. CodeCompanion collects them into *rule groups* and adds them to the chat buffer as context. To create and configure rule groups, see [Configuring Rules](/configuration/rules). ## Default Rule Group When [enabled](/configuration/rules#enabling-rules), the `default` group adds these files to the chat buffer. Files that don't exist are skipped: ```lua require("codecompanion").setup({ rules = { default = { description = "Collection of common files for all projects", files = { ".clinerules", ".cursorrules", ".goosehints", ".rules", ".windsurfrules", ".github/copilot-instructions.md", { path = "AGENT.md", parser = "claude" }, { path = "AGENTS.md", parser = "claude" }, { path = "CLAUDE.md", parser = "claude" }, { path = "CLAUDE.local.md", parser = "claude" }, { path = "~/.claude/CLAUDE.md", parser = "claude" }, }, }, }, }) ``` ## Creating Rules Rules can be markdown, [mdc](https://docs.cursor.com/en/context/rules#rule-anatomy) or plain text files. Only the `claude` and `codecompanion` [parsers](/configuration/rules#parsers) need markdown. Rules can live in your project or anywhere else on disk, as long as the path in the [rule group](/configuration/rules#rule-groups) is correct. A rules file can share other files with `@path` on its own line, and the `codecompanion` parser can also set the chat buffer's system prompt: ::: code-group ```markdown [codecompanion] # Example Rules File ## System Prompt Everything in this section is used as the system prompt in the chat buffer. So you can specify instructions: - Here - And here ## My Other Header @./lua/codecompanion/interactions/chat/tools/init.lua Everything in this section is added as context to the chat buffer, and the file above is shared too. ``` ```markdown [claude] # Example Claude Rules File @./lua/codecompanion/interactions/chat/tools/init.lua @INSTRUCTIONS.md The whole of this file is added as context to the chat buffer. The files above are shared too. ``` ::: The `codecompanion` parser only shares content under `##` headings, and removes the `@` lines from it. The `claude` parser shares the whole file, `@` lines included. ### Resolving `@` Paths CodeCompanion looks for an `@` path in this order: 1. With the `claude` parser, relative to the directory of the rules file 2. Relative to the current working directory 3. As written, which covers absolute paths starting with `/` or `~` If none of these exist, the file is skipped and a warning is logged. For example, an `@RTK.md` line in `~/.claude/CLAUDE.md` resolves to `~/.claude/RTK.md`, wherever Neovim was started from. ## Adding Rules to a Chat Buffer ### Autoload Rule groups in `autoload` are added to every new chat buffer. It defaults to `"default"`. To change it: ::: code-group ```lua [Multiple Groups] require("codecompanion").setup({ rules = { opts = { chat = { autoload = { "default", "my_project_rules" }, }, }, }, }) ``` ```lua [Replacing the Default Group] require("codecompanion").setup({ rules = { default = { description = "My default group", files = { "CLAUDE.md", "~/Code/Helpers/my_project_specific_help.md", }, }, opts = { chat = { autoload = "default", }, }, }, }) ``` ::: `autoload` also accepts a function. See [Autoload](/configuration/rules#autoload). ### Slash Command The [/rules](/usage/chat-buffer/slash-commands#rules) slash command adds a rule group to an open chat buffer. It adds one group at a time, so run it again for another. ### Action Palette The *Chat with rules ...* action in the [Action Palette](/usage/action-palette) lists every rule group in your config and opens a new chat buffer with the one you pick. ### Clearing Rules Press `gM` in the chat buffer to remove rules. **This removes every item added as rules, not only one group**. --- --- url: /usage/chat-buffer/skills.md description: >- Add agent skills to the chat buffer, so an LLM can read their instructions when a task calls for them. --- # Using Skills > \[!IMPORTANT] > Skills are only available on [HTTP adapters](/configuration/adapters-http). On [ACP adapters](/configuration/adapters-acp), use the agent's own skills through the `\` trigger, as covered in [ACP Adapters](#acp-adapters) A *skill* is a folder with a `SKILL.md` file of instructions for a specific task. CodeCompanion shares each skill's name and description with the LLM, which reads the full file only when the skill applies. To set up skill directories and groups, see [Configuring Skills](/configuration/skills). ## What Your LLM Sees Adding a skill shares the `name` and `description` from the YAML frontmatter in its `SKILL.md`: ``` The `lua-developer` skill is available: The Lua conventions for this project. Use when writing or reviewing Lua. Read `/Users/Oli/.config/codecompanion/skills/lua-developer/SKILL.md` when the skill applies and follow its instructions. ``` Sharing only the frontmatter keeps the chat buffer's context small. To read the full skill, CodeCompanion adds the `read_file` [tool](/usage/chat-buffer/agents-tools) to the chat, and `run_command` if it's enabled. The model must support function calling. > \[!NOTE] > If the `read_file` tool is disabled in your config, the skill isn't added and a warning is logged ## Adding Skills The [/skills](/usage/chat-buffer/slash-commands#skills) slash command lists every skill in your [skill directories](/configuration/skills#directories). The [/skills-group](/usage/chat-buffer/slash-commands#skills-group) slash command lists your [groups](/configuration/skills#groups) and adds all of a group's skills. It's hidden until you configure a group. To add skills to every new chat, see [Autoload](/configuration/skills#autoload). ### Pickers CodeCompanion uses [Telescope](https://github.com/nvim-telescope/telescope.nvim), [fzf-lua](https://github.com/ibhagwan/fzf-lua), [mini.pick](https://github.com/echasnovski/mini.pick) or [Snacks](https://github.com/folke/snacks.nvim), in that order, depending on which is installed. Without any of them, it falls back to `vim.ui.select`, which only selects one skill at a time. Each slash command sets its picker separately: ```lua require("codecompanion").setup({ interactions = { chat = { slash_commands = { ["skills"] = { opts = { provider = "snacks", -- Can be "default", "telescope", "fzf_lua", "mini_pick" or "snacks" }, }, ["skills-group"] = { opts = { provider = "snacks", }, }, }, }, }, }) ``` ## ACP Adapters On an [ACP adapter](/configuration/adapters-acp) such as Claude Code, the agent runs its own skills, so `/skills` and `/skills-group` are hidden. Use the `\` trigger instead. It lists the [ACP commands](/usage/chat-buffer/#completion) the agent has found for itself. A skill in `~/.claude/skills` is available either way: through `/skills` on an HTTP adapter, and through `\` when Claude Code is the agent. ## Removing a Skill Delete the skill's line from the *Context* blockquote in the chat buffer. --- --- url: /usage/chat-buffer/slash-commands.md description: >- Add files, buffers, URLs, help tags and symbols to the chat buffer, and manage sessions, MCP servers and ACP agents, with slash commands. --- # Using Slash Commands Slash commands add context to the chat buffer and act on the chat itself. Type `/` in the chat buffer to list them. They come from the `interactions.chat.slash_commands` table, plus any [prompt library](/configuration/prompt-library#options) item with `opts.is_slash_cmd = true`, such as the built-in `/commit` and `/explain`. > \[!NOTE] > An [ACP](/configuration/adapters-acp) agent such as Claude Code has its own commands, which are triggered with `\` to keep them apart. See [completion](/usage/chat-buffer/#completion) | Command | Description | | --- | --- | | [/acp\_session\_options](#acp-session-options) | Change an ACP agent's session config options | | [/buffer](#buffer) | Add open buffers | | [/command](#command) | Change the command used to start an ACP agent | | [/compact](#compact) | Replace the message history with a summary | | [/fetch](#fetch) | Add the contents of a URL | | [/file](#file) | Add files, images or PDFs | | [/file-from-url](#file-from-url) | Download a file and add it | | [/fork](#fork) | Copy the chat into a new chat buffer | | [/help](#help) | Add content from Vim help tags | | [/mcp](#mcp) | Start and stop MCP servers | | [/mcp-prompts](#mcp-prompts) | Add a prompt from an MCP server | | [/now](#now) | Insert the current date and time | | [/rename](#rename) | Rename the chat | | [/resume](#resume) | Restore a previous session | | [/rules](#rules) | Add a rules group | | [/save](#save) | Save the chat as a session | | [/share](#share) | Share the chat as a GitHub Gist | | [/skills](#skills) | Add skills | | [/skills-group](#skills-group) | Add a group of skills | | [/symbols](#symbols) | Add a symbolic outline of a file | | [/tools](#tools) | Add tools and tool groups | Some commands only appear for one type of adapter. The section for each command says which. ## /acp\_session\_options > \[!NOTE] > ACP adapters only The *acp\_session\_options* slash command changes an agent's [session config options](https://agentclientprotocol.com/protocol/session-config-options), such as its mode or reasoning level. ## /buffer The *buffer* slash command adds the contents of one or more open buffers to the chat buffer. It works with the default picker, Telescope, fzf-lua, mini.pick and Snacks. By default, an added buffer is [synced](/configuration/chat-buffer#syncing) by its diff, so the LLM sees your changes on every turn. Set `opts.default_params` to `"all"` to send the whole buffer instead. In the [CLI prompt input](/usage/cli#slash-commands), this command inserts `@path` references instead of buffer contents. ## /command > \[!NOTE] > ACP adapters only The *command* slash command switches the command used to start an ACP agent, such as one that runs the agent with a specific flag. **Switching commands resets the conversation with the agent**. ## /compact > \[!NOTE] > HTTP adapters only The *compact* slash command replaces the chat's message history with a summary, based on [Claude Code's](https://code.claude.com/docs/en/slash-commands#built-in-slash-commands) feature of the same name. You confirm before the summary is generated. The system prompt and rules are kept. Files, buffers and images are replaced with placeholders naming each one, and everything else is summarised. See [compaction](/architecture#compaction) for the details. ## /fetch > \[!TIP] > To understand a Neovim plugin better, send its `config.lua` to your LLM with `/fetch` alongside your prompt The *fetch* slash command adds the contents of a URL to the chat buffer. By default, the [MarkItDown](https://github.com/microsoft/markitdown) CLI converts the page into Markdown, and also handles local files and documents such as PDF and DOCX. The [Jina](https://jina.ai) adapter is an alternative that needs nothing installed locally: ```lua require("codecompanion").setup({ interactions = { chat = { slash_commands = { ["fetch"] = { opts = { adapter = "jina", -- Can be "markitdown" or "jina" }, }, }, }, }, }) ``` After a fetch, you're asked whether to cache the page. Once anything is cached, `/fetch` asks whether to enter a URL or pick from the cache. MarkItDown times out after two minutes. To change this: ```lua require("codecompanion").setup({ adapters = { http = { extend = { markitdown = { opts = { timeout = 300000, -- milliseconds }, }, }, }, }, }) ``` ## /file The *file* slash command adds the contents of one or more files in the current working directory to the chat buffer. It works with the default picker, Telescope, fzf-lua, mini.pick and Snacks. In most pickers, `` selects a file and `` marks several. [Context formatters](/configuration/chat-buffer#context-formatters) can reshape a file's content before the LLM sees it. In the [CLI prompt input](/usage/cli#slash-commands), this command inserts `@path` references instead of file contents. **Searching Other Directories** To search other directories alongside the current working directory: ```lua require("codecompanion").setup({ interactions = { chat = { slash_commands = { ["file"] = { opts = { dirs = { "~/notes", "../shared-library" }, }, }, }, }, }, }) ``` Paths can be relative or start with `~`. **Images** A GIF, JPEG, PNG or WebP image is sent to the LLM as an image rather than as file content. If the adapter doesn't support vision, the image isn't added. **PDFs** A PDF is base64 encoded and sent to the LLM as a document ([#3218](https://github.com/olimorris/codecompanion.nvim/pull/3218)). If the adapter doesn't support documents, the PDF isn't added. These HTTP adapters support them: * Anthropic * Copilot, with OpenAI models only * Gemini * OpenAI * OpenAI (Legacy) * OpenRouter ## /file-from-url The *file-from-url* slash command downloads a file and adds it to the chat buffer, in the same way as [/file](#file). Images and PDFs are sent as attachments and anything else as file content, with the URL shown in place of the file path. A webpage is handed to [/fetch](#fetch) instead. ## /fork > \[!NOTE] > HTTP adapters only The *fork* slash command copies the chat into a new chat buffer, keeping the message history, tools and context. Use it to branch a conversation and try different prompts, models or adapters without losing the original. You're asked for a title, which defaults to the current one. To save every fork as a [session](/configuration/sessions) as soon as it's created: ```lua require("codecompanion").setup({ interactions = { chat = { slash_commands = { ["fork"] = { opts = { auto_save_session = true, }, }, }, }, }, }) ``` ## /help The *help* slash command searches Vim help tags (`:h helpfile`) and adds the matching help content to the chat buffer. It works with Telescope, fzf-lua, mini.pick and Snacks, but not the default picker. If a help file is longer than `opts.max_lines` (128 by default), you're asked whether to trim it to that many lines around the tag. ## /mcp The *mcp* slash command starts and stops [MCP](/configuration/mcp) servers. **This applies globally, so starting or stopping a server affects every chat buffer**. Depending on your picker, several can be toggled at once. ## /mcp-prompts The *mcp-prompts* slash command adds a [prompt](https://modelcontextprotocol.io/specification/2025-11-25/server/prompts) from a running MCP server to the chat buffer, ready for you to edit before sending. After you select a prompt, you're asked for each of its arguments in turn. Optional arguments can be left blank, and cancelling at any point adds nothing. > \[!NOTE] > Only the text from a prompt's `user` messages is added. Images, resources and `assistant` messages are skipped ## /now The *now* slash command inserts the current date and time into the chat buffer. ## /rename > \[!NOTE] > HTTP adapters only The *rename* slash command changes the chat's title, which helps you tell chats apart in the [action palette](/usage/action-palette). ## /resume The *resume* slash command lists your past sessions and restores the selected one into a chat buffer. What it lists depends on the adapter: * **HTTP** - The [sessions](/configuration/sessions) saved to disk. If the current chat has no messages, it's replaced by the restored one * **ACP** - The agent's own sessions, if it supports the `session/list` and `session/load` capabilities > \[!NOTE] > On an ACP adapter, `/resume` only works before you've sent a message ## /rules The *rules* slash command adds a [rules](/usage/chat-buffer/rules) group to the chat buffer. It's also available in the [CLI prompt input](/usage/cli#slash-commands). ## /save > \[!NOTE] > HTTP adapters only, and hidden when sessions are disabled The *save* slash command saves the chat to disk as a [session](/configuration/sessions), which you can restore later with [/resume](#resume). You're asked for a title if the chat doesn't have one. By default, a chat isn't saved until you use `/save`. After that, it's saved again after every response. Turn on `autosave` in the [session config](/configuration/sessions) to save every chat automatically. ## /share The *share* slash command shares the chat as a secret [GitHub Gist](https://gist.github.com) and copies its URL to the clipboard. It needs a GitHub token with the `gist` scope: ```lua require("codecompanion").setup({ interactions = { chat = { slash_commands = { ["share"] = { opts = { token = os.getenv("GITHUB_GIST_TOKEN"), }, }, }, }, }, }) ``` ## /skills The *skills* slash command adds [skills](/usage/chat-buffer/skills) to the chat buffer. Depending on your picker, several can be selected at once. ## /skills-group The *skills-group* slash command adds a [group](/configuration/skills#groups) of skills in one go. It's hidden if you haven't configured any groups. ## /symbols > \[!NOTE] > If a filetype isn't supported, consider a PR adding the Tree-sitter queries from [aerial.nvim](https://github.com/stevearc/aerial.nvim) The *symbols* slash command uses Tree-sitter to build a symbolic outline of a file, sharing its structure with the LLM for fewer tokens than the full content. The queries come from aerial.nvim, and the supported filetypes are listed in the [queries directory](https://github.com/olimorris/codecompanion.nvim/tree/main/queries). It works with the default picker, Telescope, fzf-lua, mini.pick and Snacks, and several files can be selected at once. ## /tools The *tools* slash command adds [tools and tool groups](/usage/chat-buffer/agents-tools) to the chat buffer without leaving an `@{tool}` reference in your message. Depending on your picker, several can be selected at once. Tools already in the chat aren't listed. It only appears for HTTP adapters. --- --- url: /usage/cli.md description: >- Share context and send prompts from Neovim to CLI agents like Claude Code and Codex. --- # Using the CLI The *CLI interaction* runs agents with a command-line interface, such as [Claude Code](https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview) and [Codex](https://github.com/openai/codex), in a Neovim terminal. Sharing context with an agent in a terminal is slow: switch to it, type `@`, search for the file, or copy and paste a snippet. CodeCompanion sends context from the buffer you're in, in a few keystrokes. ## Starting an Agent Agents are defined in your config. See [Configuring the CLI](/configuration/cli#agents) to add one. To start the agent set at `interactions.cli.agent`: ``` :CodeCompanionCLI ``` To start a different one: ``` :CodeCompanionCLI agent=codex ``` `require("codecompanion").toggle()` shows or hides it, as it does a chat buffer, and `{` and `}` cycle through every chat and CLI agent. ## Workflow These keymaps cover the most common ways of working with an agent. ### Prompting the Agent To write a prompt from any buffer: ```lua vim.keymap.set({ "n", "v" }, "cp", function() return require("codecompanion").cli({ prompt = true }) end, { desc = "Prompt the CLI agent" }) ``` In normal mode, this opens the [prompt input](#prompts). In visual mode, the selection is sent along with your prompt. ### Adding Context To share the current buffer, or the visual selection, without writing a prompt: ```lua vim.keymap.set({ "n", "v" }, "ca", function() return require("codecompanion").cli("#{this}", { focus = false }) end, { desc = "Add context to the CLI agent" }) ``` `focus = false` keeps your cursor where it is, so you can move through your code adding context before you write the prompt. ### Fixing LSP Diagnostics To send the current buffer's diagnostics and ask the agent to fix them: ```lua vim.keymap.set("n", "cd", function() return require("codecompanion").cli("#{diagnostics} Can you fix these?", { focus = false, submit = true }) end, { desc = "Send diagnostics to CLI agent" }) ``` ### Fixing Failing Tests To send the output of the most recent terminal, such as a failing test run: ```lua vim.keymap.set("n", "ct", function() return require("codecompanion").cli("#{terminal} Sharing the output from the terminal. Can you fix it?", { focus = false, submit = true }) end, { desc = "Send terminal output to CLI agent" }) ``` ## Sending Context ### Visual Selection Select some code, then: ``` :CodeCompanionCLI Can you explain this code? ``` Or with Lua: ```lua require("codecompanion").cli("Can you explain this code?") ``` ### Editor Context [Editor context](/usage/chat-buffer/editor-context) works as it does in the chat buffer, such as `#{buffer}`, `#{buffers}` and `#{diagnostics}`: ``` :CodeCompanionCLI Can you explain #{buffers}? ``` The agent receives: ```log ❯ Can you explain the open buffers: @your_file_path @your_other_file_path? ``` `#{this}` is only available in the CLI interaction. It's the current buffer in normal mode and the selection in visual mode: ``` :CodeCompanionCLI What does #{this} do? ``` In normal mode, the agent receives: ```log ❯ What does @your_file_path do? ``` With a visual selection: `````log ❯ What does the selected code in @your_file_path do? - Selected code from @your_file_path (lines 3-4): ````lua local new_set = MiniTest.new_set local T = new_set() ```` ````` > \[!NOTE] > Agents such as Claude Code and Codex read `@path` references as files ### Prompts For a longer prompt, open the *prompt input*: ``` :CodeCompanionCLI Ask ``` Or with Lua: ```lua require("codecompanion").cli({ prompt = true }) ``` The prompt input is a `codecompanion_input` buffer with [editor context](#editor-context) and [slash commands](#slash-commands). Write it with `:w` to send the prompt, or `:w!` to send and submit it. Press `` and `` to scroll through earlier prompts. ### Slash Commands The prompt input supports the [/buffer](/usage/chat-buffer/slash-commands#buffer) and [/file](/usage/chat-buffer/slash-commands#file) slash commands. Type `/` to open the completion menu. Rather than sharing a file's contents, they insert an `@path` reference, one per line: ```markdown @./lua/codecompanion/init.lua @./lua/codecompanion/config.lua ``` ### Auto-Submit By default, a prompt is typed into the agent but not submitted, so you can review it before pressing enter. To submit it straight away, use the bang form of the command: ``` :CodeCompanionCLI! #{diagnostics} Can you fix these? ``` Or pass `submit = true`: ```lua require("codecompanion").cli("#{diagnostics} Can you fix these?", { submit = true }) ``` ## API Reference `require("codecompanion").cli()` takes a prompt, an options table or both: ```lua -- Start a new agent require("codecompanion").cli() -- Start a new agent with options require("codecompanion").cli({ agent = "claude_code" }) -- Send a prompt to the last agent, starting one if needed require("codecompanion").cli("Can you explain this code?") -- Send a prompt with options require("codecompanion").cli("Fix #{diagnostics}", { submit = true, focus = false }) ``` ### Options | Option | Default | Description | | --- | --- | --- | | `agent` | `interactions.cli.agent` | The agent to use. A prompt goes to a running instance of it, if there is one | | `focus` | `true` | Open the agent's window and move the cursor to it | | `submit` | `false` | Submit the prompt so the agent starts working | | `prompt` | `false` | Open the prompt input, filled with the prompt if one is given | | `width` | From your config | The window's width | | `height` | From your config | The window's height | --- --- url: /usage/code-review.md description: >- Leave comments on an agent's changes where the code is, send them all at once, and iterate in rounds - a pull request review, in Neovim. --- # Using Code Reviews > \[!IMPORTANT] > Code reviews are in **beta**, so the workflow below may change Reviewing an agent's work usually means hunting through the codebase for its changes, then copying snippets and comments back into the chat. *Code reviews* let you step through an agent's changes, accept or revert them, and comment on anything you want it to address - a pull request review, in Neovim. You don't need the review window to leave a comment. Flag a line from **any buffer** as you read your code, and it's sent to the agent with the rest of your review. Code reviews work with CodeCompanion's own tools, ACP agents and CLI agents like Claude Code running outside of Neovim. ## How It Works ```mermaid sequenceDiagram participant U as User participant C as Chat Buffer participant A as Agent participant G as Git U->>C: Writes prompt C->>A: Sends prompt C->>G: Snapshot the worktree A->>A: Edits files A->>C: Returns response U->>G: :CodeCompanionCodeReview G->>U: The review window, one row per changed file and hunk loop Step through the hunks alt Change is fine U->>U: ga to accept it else Change is wrong U->>U: gr to revert it else Change needs work U->>U: gc to comment on it end end U->>C: Shares comments with #35;{code_review} C->>A: Sends comments C->>G: The round closes off Note over U,G: The next review only shows what the agent changes in response ``` On the first prompt of a round, CodeCompanion snapshots the worktree to `refs/worktree/codecompanion/baseline`. A review is the diff between that snapshot and your files, so it covers every change, however it was made. Prompting again before you review leaves the snapshot where it is, so edits accumulate rather than disappear. A round closes off when you send or share your comments, or clear every hunk from the review window. The next review only shows what changed after that. > \[!IMPORTANT] > Snapshots use an index owned by CodeCompanion, so your staged changes are never touched ## Commands | Command | Description | | --- | --- | | `:CodeCompanionCodeReview` | Open the review window | | `:CodeCompanionCodeReview Branch` | Review every change on the branch since it left the default branch | | `:CodeCompanionCodeReview Comment` | Comment on the current line or visual selection, or edit the comment already there | ## The Review Window The review window opens in its own tab. The **checklist** on the left lists each changed file and its hunks, and the **review pane** on the right shows the whole file with the changes highlighted. | Keymap | Description | | --- | --- | | `ga` | Accept the hunk under the cursor, or every hunk in the file when you're on a file row | | `gr` | Revert the hunk under the cursor, putting the file back as it was | | `gc` | Comment on the line under the cursor | | `gC` | Edit the pending comments by hand | | `gs` | Share the review with an agent outside of CodeCompanion | | `u` | Undo the last accept or revert | | `i` / `I` | Open the real file at this line, to edit it yourself | | `]h` / `[h` | Move to the next or previous row in this file | | `?` | List these keymaps | Accepted and reverted hunks leave the list, and a file disappears with its last hunk. Files with errors are listed first, then the files with the most changes. Lockfiles and other files you never want to see can be [auto-accepted](/configuration/code-review#auto-accepting-files). Neighbouring hunks are grouped into one row. Hunks in the same function are grouped using Tree-sitter, and the row is named for it: `+6 -6 in build_diff`, or `new build_diff` when the whole function is new. A row with a single change shows the changed line. > \[!NOTE] > The review pane isn't writable. Use `i` or `I` to edit the real file at the matching line ## Reviewing a Branch To review everything the current branch has changed, including uncommitted work: ``` :CodeCompanionCodeReview Branch ``` A round only shows what the agent changed since your last review. A branch review shows the whole branch in one pass, such as before you open a pull request, including anything changed before a snapshot was taken. Pending comments are kept. Once you've cleared the last row, reviews go back to following the agent's rounds. ## Commenting Comments can be left from **anywhere** in Neovim. Put your cursor on a line, or make a visual selection, in any buffer: ``` :CodeCompanionCodeReview Comment ``` In the review window, `gc` does the same. Submit with the same keymaps as the chat buffer, and the comment shows as virtual text wherever the file is open. Comments are stored against the file, the lines and the code on them. A comment doesn't settle a hunk, so you can accept a change and still comment on it. In the next review, a row you commented on ends in `↳` and the review pane shows what you asked above the change. ### Editing and Deleting Use `gc` or `:CodeCompanionCodeReview Comment` on the same line to edit a comment. **Submitting it empty deletes it**. `gC` opens every pending comment in one file. ### Sending Use the [code\_review](/usage/chat-buffer/editor-context#code-review) editor context in a chat buffer: ```md Please action #{code_review} ``` The LLM receives each comment with its path, lines and code. A shorter version is added to the chat buffer so you can see what was sent. ## Working in the CLI Agents that CodeCompanion didn't start, such as Claude Code in a separate terminal, can be reviewed too: 1. Let the agent work 2. `:CodeCompanionCodeReview` and leave comments with `gc` 3. `gs` to move your comments to a `review.md` file and copy its path 4. Paste the path into the agent: ``` Please action my code review: /path/to/review.md ``` > \[!TIP] > The `review.md` path never changes for a repository, so you can reference it in a `CLAUDE.md` or `AGENTS.md` file If the agent runs in CodeCompanion's [CLI interaction](/usage/cli), use `#{code_review}` in the prompt instead. ## Bringing Your Own Diff `refs/worktree/codecompanion/baseline` is a normal git ref, so any diff plugin can read it: ``` :DiffviewOpen refs/worktree/codecompanion/baseline :Gitsigns change_base refs/worktree/codecompanion/baseline ``` > \[!WARNING] > Comment from the **working file**, not the baseline side of a diff ## Parallel Agents Each [git worktree](https://git-scm.com/docs/git-worktree) gets its own snapshot and comments, so agents in separate worktrees never share a review. ## Without Git Without git there's nothing to review against. `:CodeCompanionChat Changes` still lists the files an agent edited, and comments work as normal. ## Limitations * Comments are stored against line numbers, which drift if the file changes a lot before you send them. The code snippet sent with each comment usually gives the agent enough context * Closing a round doesn't discard pending comments. Use `gC` to delete them --- --- url: /usage/inline.md description: >- Edit code directly in a Neovim buffer with CodeCompanion's inline interaction, with diff review and visual selections. --- # Using the Inline Interaction As per the [Getting Started](/getting-started#editing-inline) guide, the inline interaction lets the LLM edit the current buffer directly. Run `:CodeCompanion ` to let it edit the whole buffer, or make a visual selection first to limit its edits to those lines. Running `:CodeCompanion` on its own opens an input box to write your prompt in, with the adapter and model that will answer in its title. You can also call inline prompts from the [prompt library](/configuration/prompt-library) by their alias, such as `:'<,'>CodeCompanion /docstrings`. ## How Edits Work The current buffer is shared with the LLM, which edits it by calling the `edit_file` tool, once per change. What it can edit depends on your selection: * **No selection** - The LLM can edit anything it's been shared * **Visual selection** - The LLM can only edit the selected lines. The rest of the buffer is still shared, so it can see imports and surrounding code If a buffer is over the [context limit](/configuration/inline#context-limit), only the lines around your cursor, or your selection, are shared. Without a selection, those are also the only lines the LLM can edit. If an edit fails, for example because the text it targets isn't in the buffer, the error is sent back to the LLM once so it can try again. If it fails a second time, nothing is changed and the error is logged. If you ask a question rather than for a change, such as *"what does this function do?"*, the LLM replies without editing and the reply opens in a float. The cursor moves into the float so you can scroll a long reply, and `q` closes it. By default, the LLM's edits are shown as a diff in the buffer, which you can review a hunk at a time. Press `?` in the diff to see these keymaps: | Keymap | Action | |---|---| | `}` / `{` | Move to the next or previous hunk | | `ga` | Accept the hunk under the cursor | | `gr` | Reject the hunk under the cursor | | `u` | Undo your own edits to the buffer, then the last hunk you accepted or rejected | | `g2` | Accept the hunks that are left | | `g3` | Reject the hunks that are left | | `g1` | Accept the hunks that are left, and every future edit to this buffer | **Editing the buffer during a review pauses hunk actions**, as they would overwrite your edits. Press `u` to undo your edits and carry on, or `g2` to keep them along with the hunks that are left. Once every hunk has been accepted or rejected, the diff closes. Pressing `u` afterwards undoes the whole edit in one step, however many hunks you resolved, and rejecting every hunk leaves nothing to undo. The diff can be turned off with `display.diff.enabled`, the banner above the current hunk hidden with `display.diff.show_banner`, and the keymaps changed in `interactions.shared.keymaps`. ## Input Box The input box has its own keymaps: | Keymap | Action | |---|---| | `` or `` | Send the prompt | | `q` or `` | Close the input box, keeping what you've written | | `` or `` | [Continue the last conversation](#continuing-a-conversation), or step through earlier prompts | | `ga` | Change the adapter and model | While the LLM is working, press `q` in the buffer to stop the request. ## Adapters The inline interaction works with [HTTP adapters](/configuration/adapters-http) and [ACP agents](/configuration/adapters-acp) such as Claude Code and Codex. **An HTTP adapter's model must support tool calling**, as that's how the LLM edits the buffer. Press `ga` in the input box to pick an adapter and model. Your choice is remembered for that buffer, so later inline prompts in it use the same adapter and model until you pick again. Other buffers keep using `interactions.inline.adapter`. For a one-off, include the adapter in your prompt with `adapter=*`. For example `:<','>CodeCompanion adapter=deepseek can you refactor this?`. This isn't remembered, and can be combined with editor context. **ACP Agents** An agent edits a temporary copy of the buffer rather than the buffer itself, and its edits are brought back for you to review. It can read and search your project, but it can't edit any other file or run commands. Each agent works on one inline prompt at a time, and your [rules and skills](/configuration/inline#rules-and-skills) aren't sent to it, as it loads its own. ## Continuing a Conversation Each buffer remembers its last inline conversation, so you can follow up on a reply or ask for another change without starting again. Press `` in the input box to continue it, and the title changes to `Continue` with the conversation's adapter and model. Press `` again to step back through the prompts you sent, and `` to step forward and then out to a new conversation. A fresh copy of the buffer goes with each prompt, so the LLM sees any edits you've accepted, rejected or made yourself since. A continued conversation keeps its adapter and model, so `ga` only works once you've pressed `` to start a new one. With an ACP agent, the agent's session carries on, unless the agent has restarted. Sending a prompt without continuing replaces the buffer's conversation with a new one. Without a conversation to continue, `` and `` step through the last 20 prompts you've sent. ## Editor Context The inline interaction allows you to send context alongside your prompt via the notion of editor context. That is, context that relates to your current Neovim session: * `chat` - shares the LLM's messages from the last chat buffer * `clipboard` - shares the data on your clipboard with the LLM Include them in your prompt, for example `:CodeCompanion #{clipboard} use this function in the buffer`. Multiple context items can be sent as part of the same prompt, and you can add your own as per the [configuration](/configuration/inline#editor-context). ## Limitations * A selection is limited by whole lines, so selecting part of a line lets the LLM edit all of it. * If the buffer changes while the LLM is responding, its edits are discarded. If the buffer is closed, its reply is discarded too. * When continuing a conversation with an HTTP adapter, the earlier turns count towards the model's input limit. Fewer lines of the buffer are shared as the conversation grows, and once your selection no longer fits, you need to start a new conversation. --- --- url: /usage/prompt-library.md description: >- Run prompts from your prompt library with a keymap, the command line or a slash command in the chat buffer. --- # Using the Prompt Library The *prompt library* holds reusable prompts, both the ones CodeCompanion ships with and any you [write yourself](/configuration/prompt-library). Run them from the [Action Palette](/usage/action-palette), a keymap, the command line or the chat buffer. ## Keymaps To map a prompt to a key, pass its `alias` to `prompt()`: ```lua vim.keymap.set("n", "d", function() require("codecompanion").prompt("docs") end, { noremap = true, silent = true }) ``` ## Command Line Any prompt with an `alias` can be run from the command line: ``` :CodeCompanion /docs ``` ## Slash Commands In the chat buffer, type `/` followed by the alias. Only chat prompts with `is_slash_cmd = true` in their `opts` appear here. Any [tools](/configuration/prompt-library#tools) and [skills](/configuration/prompt-library#skills) the prompt declares are added to the chat buffer before its content is inserted. --- --- url: /usage/workflows.md description: >- Run a chain of prompts that CodeCompanion sends to the LLM in turn, from the Action Palette. --- # Using Workflows A *workflow* is a series of prompts that CodeCompanion sends to the LLM one turn at a time. Combined with [tools](/usage/chat-buffer/agents-tools), a workflow can automate a loop such as editing a file, running the tests and fixing what fails. Workflows started as a way to bring reflection and planning prompts into the plugin, as described in [Issue 242 of The Batch](https://www.deeplearning.ai/the-batch/issue-242/). ## Running a Workflow Open the [Action Palette](/usage/action-palette) and select a workflow. CodeCompanion ships with one, `Code workflow`. > \[!NOTE] > Workflows can only be started from the Action Palette To write your own, see [workflows](/configuration/prompt-library#workflows) in the prompt library and [Extending with Agentic Workflows](/extending/agentic-workflows). --- --- url: /usage/ui.md description: >- Style CodeCompanion's highlight groups and read chat metadata to build your own statusline. --- # Using the UI CodeCompanion changes as little of your UI as it can. Instead, it exposes chat metadata, highlight groups and [events](/usage/events) for you to build on. See [Extending the UI](/extending/ui) for examples. ## Metadata `_G.codecompanion_chat_metadata` holds the state of every open chat buffer, keyed by buffer number: | Key | Description | | --- | --- | | `adapter` | The adapter's `name`, `model`, `model_info` and `type` | | `config_options` | The agent's configuration options, for ACP adapters | | `context_items` | Number of context items in the chat | | `cycles` | Number of turns, from your message to the LLM's response | | `id` | The chat's ID | | `title` | The chat's title, once it has one | | `tokens` | Running total of tokens | | `tools` | Number of tools in the chat | `_G.codecompanion_current_context` holds the number of the buffer that `#{buffer}` points at. The video at the top of this page shows the metadata in a statusline. ## Highlight Groups Every group links to a built-in group by default, so your colourscheme styles it until you override it. **Chat buffer** | Group | Description | | --- | --- | | `CodeCompanionChatHeader` | Headers | | `CodeCompanionChatSeparator` | Separators between headers | | `CodeCompanionChatInfo` | Information messages | | `CodeCompanionChatWarn` | Warning messages | | `CodeCompanionChatError` | Error messages | | `CodeCompanionChatSubtext` | Text under an information, warning or error message | | `CodeCompanionChatTokens` | Virtual text showing the token count | | `CodeCompanionChatFold` | Folds | | `CodeCompanionChatEditorContext` | Editor context, such as `#{buffer}` | | `CodeCompanionChatTool` | Tools, such as `@{agent}` | | `CodeCompanionChatToolPending` | A tool waiting to run | | `CodeCompanionChatToolInProgress` | A running tool | | `CodeCompanionChatToolSuccessIcon` | The icon of a tool that succeeded | | `CodeCompanionChatToolFailureIcon` | The icon of a tool that failed | **Diffs** | Group | Description | | --- | --- | | `CodeCompanionDiffAdd` | Added lines | | `CodeCompanionDiffDelete` | Deleted lines | | `CodeCompanionDiffText` | Added words within a changed line | | `CodeCompanionDiffTextDelete` | Deleted words within a changed line | | `CodeCompanionDiffBanner` | The keymap banner, aligned to the right of the window | | `CodeCompanionDiffBannerInline` | The keymap banner, on its own line above the diff | **Code reviews** | Group | Description | | --- | --- | | `CodeCompanionCodeReviewHeader` | The round summary at the top of the review window | | `CodeCompanionCodeReviewPath` | File paths in the checklist | | `CodeCompanionCodeReviewAdded` | Added lines | | `CodeCompanionCodeReviewRemoved` | Removed lines | | `CodeCompanionCodeReviewComment` | Comments | | `CodeCompanionCodeReviewSent` | Comments from the last review, shown above the changes that answer them | **Other** | Group | Description | | --- | --- | | `CodeCompanionCLIPath` | `@path` references in the CLI prompt input | | `CodeCompanionVirtualText` | All other virtual text | --- --- url: /configuration/action-palette.md description: >- Change how the Action Palette looks, which picker it uses and which built-in items it lists. --- # Configuring the Action Palette The [Action Palette](/usage/action-palette) lists CodeCompanion's actions, such as starting a chat or switching to an open one, alongside the prompts from the [prompt library](/configuration/prompt-library). ## Layout The palette is configured under `display.action_palette`. The defaults are: ```lua require("codecompanion").setup({ display = { action_palette = { width = 95, height = 10, prompt = "Prompt ", -- Shown when CodeCompanion asks for your input provider = "default", -- Can be "default", "telescope", "fzf_lua", "mini_pick" or "snacks" opts = { show_preset_actions = true, show_preset_prompts = true, title = "CodeCompanion actions", }, }, }, }) ``` If you don't set a `provider`, CodeCompanion uses the first one installed out of [Telescope](https://github.com/nvim-telescope/telescope.nvim), [fzf-lua](https://github.com/ibhagwan/fzf-lua), [mini.pick](https://github.com/nvim-mini/mini.pick) and [Snacks](https://github.com/folke/snacks.nvim), then falls back to `default`. `width` and `height` only apply to the `default` provider. ## Options The `opts` table takes: | Option | Description | | --- | --- | | `show_preset_actions` | Show the [actions](/usage/action-palette#actions), such as `Chat` and `Open chats ...` | | `show_preset_prompts` | Show the [prompts](/usage/action-palette#built-in-prompts) that ship with CodeCompanion | | `title` | The title of the palette | --- --- url: /configuration/adapters-acp.md description: >- Connect CodeCompanion to agents such as Claude Code, Codex and Gemini CLI over the Agent Client Protocol. --- # Configuring ACP Adapters ACP adapters connect CodeCompanion to an agent, such as Claude Code or Codex, over the [Agent Client Protocol](https://agentclientprotocol.com). CodeCompanion starts the agent as a separate process, and the agent brings its own tools, approvals and context management. ACP adapters sit under `adapters.acp` and share most of their configuration with [HTTP adapters](/configuration/adapters-http), so read the two pages together. ## Built-in Adapters | Adapter | Agent | Command | | --- | --- | --- | | `auggie_cli` | [Auggie CLI](#setup-auggie-cli-from-augment-code) | `auggie --acp` | | `cagent` | [Cagent](#setup-cagent) | `cagent acp basic_agent.yaml` | | `claude_code` | [Claude Code](#setup-claude-code) | `claude-agent-acp` | | `cline_cli` | [Cline CLI](#setup-cline-cli) | `cline --acp` | | `codex` | [Codex](#setup-codex) | `codex-acp` | | `copilot_acp` | [Copilot CLI](#setup-copilot-cli) | `copilot --acp --stdio` | | `cursor_cli` | [Cursor CLI](#setup-cursor-cli) | `agent acp` | | `gemini_cli` | [Gemini CLI](#setup-gemini-cli) | `gemini --experimental-acp` | | `goose` | [Goose](#setup-goose-cli) | `goose acp` | | `kilocode` | [Kilo Code](#setup-kilo-code) | `kilo acp` | | `kimi_cli` | [Kimi CLI](#setup-kimi-cli) | `kimi acp` | | `kiro` | [Kiro CLI](#setup-kiro-cli) | `kiro-cli acp` | | `mistral_vibe` | [Mistral Vibe](#setup-mistral-vibe) | `vibe-acp` | | `opencode` | [OpenCode](#setup-opencode) | `opencode acp` | The command must be on your `PATH`. ## Customising an Adapter There are two ways to customise a built-in adapter, and this page uses both: * **Function** - For full or computed setups, such as custom `commands` or `defaults`, or values resolved when the adapter loads * **`extend` table** - For static overrides, such as credentials or a default value ::: code-group ```lua [Function] require("codecompanion").setup({ adapters = { acp = { gemini_cli = function() return require("codecompanion.adapters").extend("gemini_cli", { commands = { default = { "some-other-gemini", "--experimental-acp" }, }, defaults = { auth_method = "gemini-api-key", timeout = 20000, -- milliseconds }, env = { GEMINI_API_KEY = "cmd:op read op://personal/Gemini/credential --no-newline" }, }) end, }, }, }) ``` ```lua [Extend Table] require("codecompanion").setup({ adapters = { acp = { extend = { gemini_cli = { defaults = { auth_method = "gemini-api-key" }, env = { GEMINI_API_KEY = "cmd:op read op://personal/Gemini/credential --no-newline" }, }, }, }, }, }) ``` ::: > \[!IMPORTANT] > Each key in `extend` is the adapter's key under `adapters.acp`, not the adapter's `name` Values in `env` are resolved in the same way as for [HTTP adapters](/configuration/adapters-http#environment-variables), then passed to the agent's process as environment variables. The `auth_method` must match an ID the agent advertises. If it doesn't, the log lists the ones that are available. ## Setting a Default Adapter To use an agent for every chat: ```lua require("codecompanion").setup({ interactions = { chat = { adapter = "gemini_cli", }, }, }) ``` ## Commands An adapter starts its agent with the `default` entry in its `commands` table. `claude_code` and `gemini_cli` also have a `yolo` command, which starts the agent with `--yolo`. To add your own: ```lua require("codecompanion").setup({ adapters = { acp = { extend = { cagent = { commands = { reviewer = { "cagent", "acp", "reviewer.yaml" }, }, }, }, }, }, }) ``` To start a chat with a command other than `default`: ``` :CodeCompanionChat adapter=cagent command=reviewer ``` From inside a chat buffer, use the [/command](/usage/chat-buffer/slash-commands#command) slash command. ## Setting Default Session Config Options [Session config options](https://agentclientprotocol.com/protocol/session-config-options) are settings an agent shares at the start of a session, such as its models, modes and reasoning levels. Set their defaults in `defaults.session_config_options`, keyed by the option's category. ### Models Set the model on the interaction, or on the adapter so it applies everywhere: ::: code-group ```lua [Interactions] {4-7} require("codecompanion").setup({ interactions = { chat = { adapter = { name = "codex", model = "gpt-5.4", }, }, }, }) ``` ```lua [Adapter String] {6-10} require("codecompanion").setup({ adapters = { acp = { codex = function() return require("codecompanion.adapters").extend("codex", { defaults = { session_config_options = { model = "gpt-5.4", }, }, }) end, }, }, }) ``` ```lua [Adapter Function] {6-14} require("codecompanion").setup({ adapters = { acp = { codex = function() return require("codecompanion.adapters").extend("codex", { defaults = { session_config_options = { ---@param self CodeCompanion.ACPAdapter ---@return string model = function(self) return "gpt-5.4" end, }, }, }) end, }, }, }) ``` ::: ### Others Any other option goes in the same table: ```lua {6-11} require("codecompanion").setup({ adapters = { acp = { codex = function() return require("codecompanion.adapters").extend("codex", { defaults = { session_config_options = { mode = "Full Access", thought_level = "Xhigh", }, }, }) end, }, }, }) ``` A value matches either the option's value or its name, ignoring case. The model is set first, as it can change which other options are available. To see an agent's options and their values, open the [debug window](/usage/chat-buffer/#debug-window). ## Setting a System Prompt > \[!IMPORTANT] > This feature is in beta. It's built on an [unstable part of the ACP spec](https://agentclientprotocol.com/rfds/client-system-prompt), so it may change or break between releases Agents have their own system prompt. By default, CodeCompanion appends a single line to it: ```txt Do not use H1 or H2 headers in your response, unless writing the contents of a markdown file. ``` To replace that line with your own: ```lua require("codecompanion").setup({ adapters = { acp = { claude_code = function() return require("codecompanion.adapters").extend("claude_code", { defaults = { system_prompt = { mode = "append", -- Can be "append" or "override" content = "Always reply in British English.", }, }, }) end, }, }, }) ``` `mode` defaults to `append`. **`override` replaces the agent's own instructions, including how it uses its tools**, so only use it to supply a full replacement. `content` can also be a function, which receives the same context as the [chat system prompt](/configuration/system-prompt). To keep CodeCompanion's line and add to it: ```lua require("codecompanion").setup({ adapters = { acp = { claude_code = function() return require("codecompanion.adapters").extend("claude_code", { defaults = { system_prompt = { content = function(ctx) return ctx.default_system_prompt .. "\n\nAlways reply in British English." end, }, }, }) end, }, }, }) ``` To send nothing, set `system_prompt = false`. The system prompt is sent whenever a session starts or loads. If the agent doesn't support it, or doesn't support the mode you picked, the session starts without it. CodeCompanion notifies you when this happens to a system prompt you've set. > \[!NOTE] > Agents already read their own instruction files, such as `CLAUDE.md` and `AGENTS.md`. Put project rules there and use this for anything specific to CodeCompanion ## Configuring MCP Servers Some agents [support](https://agentclientprotocol.com/protocol/session-setup#mcp-servers) connecting to Model Context Protocol (MCP) servers. To pass on the servers in your [MCP configuration](/configuration/mcp): ```lua require("codecompanion").setup({ adapters = { acp = { claude_code = function() return require("codecompanion.adapters").extend("claude_code", { defaults = { mcpServers = "inherit_from_config", }, }) end, }, }, }) ``` Only the servers in `mcp.opts.default_servers` are passed, and setting `mcp.opts.acp_enabled = false` stops them being passed at all. The agent starts and runs the servers itself, so they don't appear in the chat buffer's context. To set the servers on the adapter instead, such as Claude Code with the [sequential-thinking](https://github.com/modelcontextprotocol/servers/tree/main/src/sequentialthinking) server over stdio: ```lua require("codecompanion").setup({ adapters = { acp = { claude_code = function() return require("codecompanion.adapters").extend("claude_code", { defaults = { mcpServers = { { name = "sequential-thinking", command = "npx", args = { "-y", "@modelcontextprotocol/server-sequential-thinking" }, env = {}, }, }, }, }) end, }, }, }) ``` ## Hiding Preset Adapters To list only the adapters in your own config, leaving out the built-in ones: ```lua require("codecompanion").setup({ adapters = { acp = { opts = { show_presets = false, }, }, }, }) ``` To hide individual adapters, see [Hiding Adapters](/configuration/adapters-http#hiding-adapters). ## Setup: Auggie CLI from Augment Code Follow the [Getting Started](https://docs.augmentcode.com/cli/overview#getting-started) guide for [Auggie CLI](https://docs.augmentcode.com/cli/overview), then select the `auggie_cli` adapter. ## Setup: Cagent To use Docker's [Cagent](https://github.com/docker/cagent): 1. [Install](https://github.com/docker/cagent?tab=readme-ov-file#installation) Cagent 2. [Create an agent](https://github.com/docker/cagent?tab=readme-ov-file#run-agents) in your repository 3. Test it with `cagent run your_agent.yaml` 4. Point the `cagent` adapter at your agent: ```lua require("codecompanion").setup({ adapters = { acp = { cagent = function() return require("codecompanion.adapters").extend("cagent", { commands = { default = { "cagent", "acp", "your_agent.yaml", }, }, }) end, }, }, }) ``` For more than one agent, add a [command](#commands) for each. ## Setup: Claude Code To use [Claude Code](https://www.anthropic.com/claude-code): 1. [Install](https://docs.anthropic.com/en/docs/claude-code/quickstart#step-1%3A-install-claude-code) Claude Code 2. [Install](https://github.com/zed-industries/claude-agent-acp) Zed's ACP adapter for Claude Code 3. Authenticate with a Claude Pro subscription or an API key, as below ### Using Claude Pro Subscription 1. Run `claude setup-token` and authorise in the browser: 2. Copy the OAuth token, shown in yellow: 3. Set it on the `claude_code` adapter: ```lua require("codecompanion").setup({ adapters = { acp = { claude_code = function() return require("codecompanion.adapters").extend("claude_code", { env = { CLAUDE_CODE_OAUTH_TOKEN = "my-oauth-token", }, }) end, }, }, }) ``` If `CLAUDE_CODE_OAUTH_TOKEN` is already exported in your shell, skip step 3. See [environment variables](/configuration/adapters-http#environment-variables) for other ways to supply the token. ### Using an API Key 1. [Create](https://console.anthropic.com/settings/keys) an API key in the Anthropic console 2. Set it on the `claude_code` adapter: ```lua require("codecompanion").setup({ adapters = { acp = { claude_code = function() return require("codecompanion.adapters").extend("claude_code", { env = { ANTHROPIC_API_KEY = "my-api-key", }, }) end, }, }, }) ``` ## Setup: Cline CLI To use [Cline CLI](https://cline.bot/cli): 1. [Install](https://docs.cline.bot/getting-started/installing-cline#cli) Cline CLI 2. Run `cline auth` 3. Select the `cline_cli` adapter ## Setup: Codex To use OpenAI's [Codex](https://openai.com/codex/), install [codex-acp](https://github.com/agentclientprotocol/codex-acp). The adapter authenticates with `OPENAI_API_KEY` from your shell by default. To set the key on the adapter, or to sign in with ChatGPT instead: ```lua require("codecompanion").setup({ adapters = { acp = { codex = function() return require("codecompanion.adapters").extend("codex", { defaults = { auth_method = "api-key", -- Can be "api-key" or "chat-gpt" }, env = { OPENAI_API_KEY = "my-api-key", }, }) end, }, }, }) ``` ## Setup: Copilot CLI [Install](https://docs.github.com/en/copilot/how-tos/copilot-cli/set-up-copilot-cli/install-copilot-cli) Copilot CLI, run `copilot` to sign in, then select the `copilot_acp` adapter. ## Setup: Cursor CLI To use [Cursor](https://www.cursor.com/): 1. Install `agent` from the [Cursor CLI documentation](https://cursor.com/docs/cli/overview) 2. Run `agent login` 3. Select the `cursor_cli` adapter ## Setup: Gemini CLI Install [Gemini CLI](https://github.com/google-gemini/gemini-cli), then choose an `auth_method`: | Method | Description | | --- | --- | | `oauth-personal` | Sign in with your Google account (default) | | `gemini-api-key` | Use `GEMINI_API_KEY` | | `vertex-ai` | Use Vertex AI | To use an API key from the [1Password CLI](https://developer.1password.com/docs/cli/get-started/): ```lua require("codecompanion").setup({ adapters = { acp = { gemini_cli = function() return require("codecompanion.adapters").extend("gemini_cli", { defaults = { auth_method = "gemini-api-key", }, env = { GEMINI_API_KEY = "cmd:op read op://personal/Gemini_API/credential --no-newline", }, }) end, }, }, }) ``` ## Setup: Goose CLI [Install](https://goose-docs.ai/docs/getting-started/installation/) and set up [Goose](https://goose-docs.ai/) CLI, then select the `goose` adapter. ## Setup: Kilo Code [Install](https://kilo.ai/docs/getting-started/installing#cli) and [configure](https://kilo.ai/docs/getting-started/setup-authentication#cli) [Kilo Code](https://kilo.ai), then select the `kilocode` adapter. To set the model, edit `~/.config/kilo/kilo.json`: ```json { "$schema": "https://kilo.ai/config.json", "model": "kilo/kilo-auto/free" } ``` ## Setup: Kimi CLI [Install](https://github.com/MoonshotAI/kimi-cli?tab=readme-ov-file#installation) Kimi CLI, run `kimi` and then `/login` to set your API key, then select the `kimi_cli` adapter. ## Setup: Kiro CLI [Install](https://kiro.dev/docs/cli/) Kiro CLI and sign in, then select the `kiro` adapter. ## Setup: Mistral Vibe [Install](https://github.com/mistralai/mistral-vibe) [Mistral Vibe](https://github.com/mistralai/mistral-vibe), run `vibe --setup` to set your API key, then select the `mistral_vibe` adapter. ## Setup: OpenCode [Install](https://opencode.ai/docs/#install) and [configure](https://opencode.ai/docs/#configure) [OpenCode](https://opencode.ai), then select the `opencode` adapter. To set the model, edit `~/.config/opencode/opencode.jsonc` or `~/.config/opencode/opencode.json`: ```json { "$schema": "https://opencode.ai/config.json", "model": "github-copilot/claude-sonnet-4.5" } ``` OpenCode doesn't send diffs for [code review](/usage/code-review) by default. To turn them on, [set its permissions](https://opencode.ai/docs/permissions/) to ask: ```json { "$schema": "https://opencode.ai/config.json", "model": "opencode-go/mimo-v2.5-pro", "permission": { "*": "ask" } } ``` ## Creating Custom ACP Adapters For an agent without a built-in adapter, define your own. This example uses a hypothetical `myagent` CLI, and the [built-in ACP adapters](https://github.com/olimorris/codecompanion.nvim/blob/main/lua/codecompanion/adapters/acp) make a good reference: ```lua require("codecompanion").setup({ adapters = { acp = { my_agent = function() local helpers = require("codecompanion.adapters.acp.helpers") return { name = "my_agent", formatted_name = "MyAgent", type = "acp", roles = { llm = "assistant", user = "user", }, commands = { default = { "myagent", "--acp", }, }, defaults = { mcpServers = {}, timeout = 20000, -- milliseconds }, parameters = { protocolVersion = 1, clientCapabilities = { fs = { readTextFile = true, writeTextFile = true }, }, clientInfo = { name = "CodeCompanion.nvim", version = "1.0.0", }, }, handlers = { lifecycle = { setup = function(self) return true end, auth = function(self) return true end, }, request = { build_messages = helpers.build_messages, }, }, } end, }, }, }) ``` Raise issues and questions about user-created adapters in the [adapter discussions](https://github.com/olimorris/codecompanion.nvim/discussions?discussions_q=is%3Aopen+label%3A%22tip%3A+adapter%22). --- --- url: /configuration/adapters-http.md description: >- Connect CodeCompanion to an LLM provider, and set its API key, model and parameters. --- # Configuring HTTP Adapters An *adapter* connects CodeCompanion to an LLM provider or an agent. HTTP adapters, set under `adapters.http`, call an LLM's API directly, with CodeCompanion supplying the system prompt, tools and agent loop. [ACP adapters](/configuration/adapters-acp), set under `adapters.acp`, connect to an agent that brings its own. Both are configured in the same way, so read the two pages together. > \[!TIP] > For a provider that isn't built in, try a [community adapter](#community-adapters) or [create your own](/extending/adapters) ## Built-in Adapters | Adapter | API key | Default model | | --- | --- | --- | | `anthropic` | `ANTHROPIC_API_KEY` | `claude-sonnet-5` | | `azure_openai` | `AZURE_OPENAI_API_KEY` and `AZURE_OPENAI_ENDPOINT` | None, set your deployment name | | `copilot` | Your Copilot sign-in | `auto` | | `deepseek` | `DEEPSEEK_API_KEY` | `deepseek-v4-flash` | | `gemini` | `GEMINI_API_KEY` | `gemini-3.1-pro-preview` | | `gemini_legacy` | `GEMINI_API_KEY` | `gemini-3.1-pro-preview` | | `huggingface` | `HUGGINGFACE_API_KEY` | `Qwen/Qwen2.5-32B-Instruct` | | `kimi` | `MOONSHOT_API_KEY` | `kimi-k2.7-code` | | `mistral` | `MISTRAL_API_KEY` | `mistral-small-latest` | | `novita` | `NOVITA_API_KEY` | `meta-llama/llama-3.1-8b-instruct` | | `ollama` | None, set `OLLAMA_HOST` for a remote server | A model from your Ollama server | | `openai` | `OPENAI_API_KEY` | `gpt-5.6-luna` | | `openai_legacy` | `OPENAI_API_KEY` | `gpt-4.1` | | `openrouter` | `OPENROUTER_API_KEY` | `openai/gpt-5.4-mini` | | `xai` | `XAI_API_KEY` | `grok-beta` | `gemini` uses Google's Interactions API and `gemini_legacy` uses `generateContent`. `openai` uses the Responses API and `openai_legacy` uses Chat Completions. For llama.cpp, LM Studio and other servers that speak the OpenAI API, extend `openai_compatible`, as covered in [Connecting an OpenAI-Compatible Provider](/guides/openai-compatible-providers). The `duckduckgo`, `jina`, `markitdown`, `serply` and `tavily` adapters power the [web search](/guides/web-search) and fetch tools rather than chat. ## Changing the Default Adapter To set the adapter for each interaction: ```lua require("codecompanion").setup({ interactions = { chat = { adapter = "anthropic", }, inline = { adapter = "copilot", }, cmd = { adapter = "deepseek", }, }, }) ``` ## Changing the Default Model Set the model on the interaction, or on the adapter so it applies everywhere: ::: code-group ```lua [For Interactions] {4-7} require("codecompanion").setup({ interactions = { chat = { adapter = { name = "openai", model = "gpt-4.1", }, }, }, }) ``` ```lua [For Adapters] {6-10} require("codecompanion").setup({ adapters = { http = { openai = function() return require("codecompanion.adapters").extend("openai", { schema = { model = { default = "gpt-4.1", }, }, }) end, }, }, }) ``` ::: ## Customising an Adapter There are two ways to customise a built-in adapter, and this page uses both: * **Function** - For full or computed setups, such as a custom `url`, `headers` or `schema`, or values resolved when the adapter loads * **`extend` table** - For static overrides, such as credentials or a default value ::: code-group ```lua [Function] require("codecompanion").setup({ adapters = { http = { anthropic = function() return require("codecompanion.adapters").extend("anthropic", { env = { api_key = "cmd:op read op://personal/Anthropic/credential --no-newline" }, }) end, }, }, }) ``` ```lua [Extend Table] require("codecompanion").setup({ adapters = { http = { extend = { anthropic = { env = { api_key = "cmd:op read op://personal/Anthropic/credential --no-newline" } }, }, }, }, }) ``` ::: > \[!IMPORTANT] > Each key in `extend` is the adapter's key under `adapters.http`, not the adapter's `name` ## Changing Adapter Parameters (Schema) An adapter's parameters, such as `model`, `temperature` and `max_output_tokens`, sit in its `schema` table. To change a default: ```lua require("codecompanion").setup({ adapters = { http = { openai = function() return require("codecompanion.adapters").extend("openai", { schema = { temperature = { default = 0.2, }, }, }) end, }, }, }) ``` A parameter's `enabled` function takes the adapter and decides whether the parameter is sent. To leave `temperature` out for Codex models: ```lua require("codecompanion").setup({ adapters = { http = { openai = function() return require("codecompanion.adapters").extend("openai", { schema = { temperature = { enabled = function(self) local model = require("codecompanion.adapters.utils").model(self) return not (model and model:find("codex")) end, }, }, }) end, }, }, }) ``` ## Adding a Custom Adapter Add your own adapter alongside the built-in ones: ```lua require("codecompanion").setup({ adapters = { http = { my_custom_adapter = function() return {} -- Your adapter end, }, }, }) ``` See [Creating Adapters](/extending/adapters) for what goes in the table. ## Background Interaction Adapters [Background interactions](/guides/background-model) send requests to an LLM without any input from you, such as to generate a chat title. They use `interactions.background.adapter`, which you can override for a single action: ```lua require("codecompanion").setup({ interactions = { background = { chat = { callbacks = { ["on_ready"] = { actions = { { path = "interactions.background.builtin.chat_make_title", adapter = { name = "copilot", model = "claude-haiku-4.5" }, }, }, }, }, }, }, }, }) ``` ## Controlling Model Choices Changing adapter with `ga` asks you to pick a model when the adapter has more than one. To use the adapter's default model without asking: ```lua require("codecompanion").setup({ adapters = { http = { opts = { show_model_choices = false, }, }, }, }) ``` Model lists fetched from a provider are cached for 1800 seconds, set with `adapters.http.opts.cache_models_for`. ## Environment Variables An adapter's `env` table holds values, such as an API key, that are resolved on every request and substituted into its URL, headers and parameters: ::: code-group ```lua{7} [Environment Variable] require("codecompanion").setup({ adapters = { http = { anthropic = function() return require("codecompanion.adapters").extend("anthropic", { env = { api_key = "MY_OTHER_ANTHROPIC_KEY", }, }) end, }, }, }) ``` ```lua{7} [Command] require("codecompanion").setup({ adapters = { http = { anthropic = function() return require("codecompanion.adapters").extend("anthropic", { env = { api_key = "cmd:op read op://personal/Anthropic/credential --no-newline", }, }) end, }, }, }) ``` ```lua{7-9} [Function] require("codecompanion").setup({ adapters = { http = { anthropic = function() return require("codecompanion.adapters").extend("anthropic", { env = { api_key = function() return my_custom_api_key_fetcher() end, }, }) end, }, }, }) ``` ```lua{7} [Schema Reference] require("codecompanion").setup({ adapters = { http = { anthropic = function() return require("codecompanion.adapters").extend("anthropic", { env = { model_for_url = "schema.model.default", }, }) end, }, }, }) ``` ```lua{7} [File] require("codecompanion").setup({ adapters = { http = { anthropic = function() return require("codecompanion.adapters").extend("anthropic", { env = { api_key = "file:~/.dotfiles/.anthropic_api_key", }, }) end, }, }, }) ``` ::: | Value | Resolves to | | --- | --- | | `"cmd:op read ..."` | The output of the shell command, such as the [1Password CLI](https://developer.1password.com/docs/cli/) or [gpg](https://github.com/olimorris/codecompanion.nvim/discussions/601) | | `"file:~/.anthropic_api_key"` | The file's contents, with a relative path read from the current working directory | | `"ANTHROPIC_API_KEY"` | The environment variable of that name | | A function | Its return value, called with the adapter | | `"schema.model.default"` | The value at that path in the adapter | | Anything else | The value as written | Commands time out after 20 seconds. To change this: ```lua require("codecompanion").setup({ adapters = { opts = { cmd_timeout = 30000, -- milliseconds }, }, }) ``` ## Disabling Compaction The `anthropic` and `openai` adapters use the provider's server-side compaction to manage context. To turn it off: ```lua require("codecompanion").setup({ adapters = { http = { anthropic = function() return require("codecompanion.adapters").extend("anthropic", { opts = { compaction = false, }, }) end, }, }, }) ``` ## Hiding Adapters Adapters in the `hidden` table are left out of the adapter picker. Set one to `false` to show it again: ```lua require("codecompanion").setup({ adapters = { http = { opts = { hidden = { my_custom_adapter = true, tavily = false }, }, }, }, }) ``` By default, `duckduckgo`, `jina`, `markitdown` and `tavily` are hidden. Hidden adapters can still be used by name, such as by the `web_search` tool. To list only the adapters in your own config, leaving out the built-in ones: ```lua require("codecompanion").setup({ adapters = { http = { opts = { show_presets = false, }, }, }, }) ``` ## Setting a Proxy To send requests through a proxy: ```lua require("codecompanion").setup({ adapters = { http = { opts = { allow_insecure = true, proxy = "socks5://127.0.0.1:9999", }, }, }, }) ``` ## Setup Examples Some of these are illustrations rather than setups the plugin actively supports. ### Azure OpenAI The `azure_openai` adapter has no default model, so set your deployment name: ```lua require("codecompanion").setup({ adapters = { http = { azure_openai = function() return require("codecompanion.adapters").extend("azure_openai", { env = { api_key = "YOUR_AZURE_OPENAI_API_KEY", endpoint = "YOUR_AZURE_OPENAI_ENDPOINT", }, schema = { model = { default = "YOUR_DEPLOYMENT_NAME", }, }, }) end, }, }, interactions = { chat = { adapter = "azure_openai", }, inline = { adapter = "azure_openai", }, }, }) ``` ### Copilot Free and Student Copilot Free and Student plans only get models [through auto model selection](https://docs.github.com/en/copilot/reference/ai-models/supported-models#supported-ai-models-per-copilot-plan). The `copilot` adapter defaults to the `auto` model, so it works without any extra config. ### llama.cpp with `--reasoning-format deepseek` To show llama.cpp's reasoning output in the chat buffer: ```lua require("codecompanion").setup({ adapters = { http = { ["llama.cpp"] = function() return require("codecompanion.adapters").extend("openai_compatible", { env = { url = "http://127.0.0.1:8080", -- replace with your llama.cpp instance api_key = "TERM", chat_url = "/v1/chat/completions", }, handlers = { parse_message_meta = function(self, data) local extra = data.extra if extra and extra.reasoning_content then data.output.reasoning = { content = extra.reasoning_content } if data.output.content == "" then data.output.content = nil end end return data end, }, }) end, }, }, interactions = { chat = { adapter = "llama.cpp", }, inline = { adapter = "llama.cpp", }, }, }) ``` ### Ollama (remotely) To connect to a remote Ollama server, set `OLLAMA_HOST`, the same variable the Ollama CLI uses: ```bash export OLLAMA_HOST="http://192.168.1.100:11434" ``` Or set the URL on the adapter, with an API key sent in an `Authorization` header if the server needs one: ```lua require("codecompanion").setup({ adapters = { http = { ollama = function() return require("codecompanion.adapters").extend("ollama", { env = { url = "https://my_ollama_url", api_key = "OLLAMA_API_KEY", }, headers = { ["Content-Type"] = "application/json", ["Authorization"] = "Bearer ${api_key}", }, }) end, }, }, }) ``` ### OpenAI Responses API The `openai` adapter uses OpenAI's [Responses API](https://platform.openai.com/docs/api-reference/responses). To use the [Chat Completions API](https://platform.openai.com/docs/api-reference/chat) instead: ```lua require("codecompanion").setup({ interactions = { chat = { adapter = "openai_legacy", }, inline = { adapter = "openai_legacy", }, }, }) ``` The `openai` adapter sends `store = false`, so OpenAI doesn't [store](https://platform.openai.com/docs/api-reference/responses/create#responses-create-store) your responses. ### OpenRouter The `openrouter` adapter supports: * Explicit prompt caching for Anthropic models * Server tools such as [web\_fetch](https://openrouter.ai/docs/guides/features/server-tools/web-fetch) and [web\_search](https://openrouter.ai/docs/guides/features/server-tools/web-search) * Reasoning [effort](https://openrouter.ai/docs/guides/best-practices/reasoning-tokens#reasoning-effort-level) levels * [Presets](https://openrouter.ai/docs/guides/features/presets) * [Provider routing](https://openrouter.ai/docs/guides/routing/provider-selection) Tool use, vision and the available parameters depend on the model you select. Set them in your config, or for a single chat in the [debug window](/usage/chat-buffer/#debug-window): ::: code-group ```lua [Presets] {7} require("codecompanion").setup({ adapters = { http = { openrouter = function() return require("codecompanion.adapters").extend("openrouter", { schema = { preset = { default = "email-copywriter" }, }, }) end, }, }, }) ``` ```lua [Provider Routing] {7-13} require("codecompanion").setup({ adapters = { http = { openrouter = function() return require("codecompanion.adapters").extend("openrouter", { schema = { provider = { default = { allow_fallbacks = true, order = { "anthropic", "openai" }, require_parameters = false, }, }, }, }) end, }, }, }) ``` ::: Each chat buffer sends its own `session_id` for [sticky sessions](https://openrouter.ai/docs/guides/best-practices/prompt-caching#using-session_id-for-sticky-sessions), which you can rename in the debug window. To fix it on the adapter instead: ```lua {6} require("codecompanion").setup({ adapters = { http = { openrouter_title_generation = function() return require("codecompanion.adapters").extend("openrouter", { opts = { session_id = "title_generation" }, }) end, }, }, }) ``` ## Community Adapters Adapters built by the community: * [DashScope](https://github.com/olimorris/codecompanion.nvim/discussions/2239) * [Fireworks.ai](https://github.com/olimorris/codecompanion.nvim/discussions/693) * [InceptionLabs - Mercury 2](https://github.com/olimorris/codecompanion.nvim/discussions/2867) * [Nvidia NIM](https://github.com/olimorris/codecompanion.nvim/discussions/2810) * [Opper](https://github.com/olimorris/codecompanion.nvim/discussions/3487) * [Venice.ai](https://github.com/olimorris/codecompanion.nvim/discussions/972) * [Vertex AI](https://github.com/viespejo/cc-adapter-vertex-ai.nvim) Raise issues and questions about them in their threads in the [adapter discussions](https://github.com/olimorris/codecompanion.nvim/discussions?discussions_q=is%3Aopen+label%3A%22tip%3A+adapter%22). --- --- url: /configuration/chat-buffer.md description: >- Set the chat buffer's adapter, completion, keymaps, editor context, slash commands and syncing. --- # Configuring the Chat Buffer The *chat buffer* is where you converse with an LLM or an agent. Its settings live under `interactions.chat`, and every option is listed in [config.lua](https://github.com/olimorris/codecompanion.nvim/blob/main/lua/codecompanion/config.lua). ## Changing Adapter The chat buffer uses the `copilot` adapter by default. To use another HTTP or ACP adapter: ```lua require("codecompanion").setup({ interactions = { chat = { adapter = { name = "anthropic", model = "claude-haiku-4-5-20251001", }, }, }, }) ``` See [ACP adapters](/configuration/adapters-acp) and [HTTP adapters](/configuration/adapters-http) for the full list. ## Completion CodeCompanion uses [blink.cmp](https://github.com/saghen/blink.cmp), [nvim-cmp](https://github.com/hrsh7th/nvim-cmp) or [coc.nvim](https://github.com/neoclide/coc.nvim), in that order, if one is installed. Otherwise, it falls back to Neovim's native completion. You can override this with: ```lua require("codecompanion").setup({ interactions = { chat = { opts = { completion_provider = "blink", -- Can be "blink", "cmp", "coc" or "default" }, }, }, }) ``` ### Triggers The characters that open completion for [editor context](/usage/chat-buffer/editor-context), [slash commands](/usage/chat-buffer/slash-commands), [tools](/usage/chat-buffer/agents-tools) and ACP commands can be changed with: ```lua require("codecompanion").setup({ opts = { triggers = { acp_slash_commands = "\\", editor_context = "#", slash_commands = "/", tools = "@", }, }, }) ``` ## Context Formatters Some files are a poor fit for an LLM as they are. A [Jupyter Notebook](https://jupyter.org/) is a large JSON document with markdown, code and base64 images embedded in it, which fills the context window quickly. A *context formatter* changes a file's content before the LLM sees it. Formatters are keyed by file extension, and apply whether the file is added with `/file`, `/buffer`, editor context, a rules file or a [sync](#syncing). CodeCompanion ships one for `ipynb` files. A formatter is a function that takes the raw content and the path, and returns the content the LLM should see. It can also be the path to a module that returns a table with a `format` function: ::: code-group ```lua [Function] require("codecompanion").setup({ context = { formatters = { sqlite = function(raw, path) return vim.fn.system({ "sqlite3", path, ".schema" }) end, }, }, }) ``` ```lua [Module] require("codecompanion").setup({ context = { formatters = { sqlite = "my_plugin.context.formatters.sqlite", }, }, }) ``` ::: CodeCompanion passes a formatter's output through as it is. Content without a formatter is wrapped in a code block, and buffers also get line numbers. Neither applies to a sync diff, which is already fenced. If a formatter errors or doesn't return a string, the raw content is used instead. ## Editor Context [Editor context](/usage/chat-buffer/editor-context), such as `#{buffer}`, shares part of your Neovim session with the LLM. It's configured under `interactions.shared.editor_context`, as it's shared with the [CLI interaction](/usage/cli). To add your own, give it a `callback` that returns the content to send: ```lua require("codecompanion").setup({ interactions = { shared = { editor_context = { ["git_branch"] = { description = "Share the current git branch", callback = function() return "The current git branch is " .. vim.fn.system("git branch --show-current") end, opts = { contains_code = false, }, }, }, }, }, }) ``` For more control, swap `callback` for `path`, pointing to a module built like those in `lua/codecompanion/interactions/shared/editor_context/`. ## Keymaps Keymaps only apply to the chat buffer. You can change any of the [defaults](/usage/chat-buffer/#keymaps), and set `opts` to pass extra `:map-arguments` to `vim.keymap.set`: ::: code-group ```lua [Chat] require("codecompanion").setup({ interactions = { chat = { keymaps = { send = { modes = { n = "", i = "" }, opts = {}, }, close = { modes = { n = "", i = "" }, opts = {}, }, }, }, }, }) ``` ```lua [Inline] require("codecompanion").setup({ interactions = { inline = { keymaps = { stop = { callback = "keymaps.stop", description = "Stop request", modes = { n = "q" }, }, }, }, }, }) ``` ```lua [Diff] require("codecompanion").setup({ interactions = { shared = { keymaps = { always_accept = { callback = "keymaps.always_accept", modes = { n = "g1" }, }, accept_change = { callback = "keymaps.accept_change", modes = { n = "g2" }, }, reject_change = { callback = "keymaps.reject_change", modes = { n = "g3" }, }, next_hunk = { callback = "keymaps.next_hunk", modes = { n = "}" }, }, previous_hunk = { callback = "keymaps.previous_hunk", modes = { n = "{" }, }, }, }, }, }) ``` ::: The keymaps under `interactions.shared` apply to diffs and tool approvals in both the chat buffer and the inline interaction. They also include `view_diff` (`gv`) and `cancel` (`g4`). To disable a keymap, set it to `false`: ```lua require("codecompanion").setup({ interactions = { chat = { keymaps = { send = false, close = false, }, }, }, }) ``` ## Prompt Decorator A *prompt decorator* changes your message before it's sent to the LLM. The GitHub Copilot prompt in VS Code, for example, wraps the user's message in `` tags to separate it from the rest of the context. To do the same: ```lua require("codecompanion").setup({ interactions = { chat = { opts = { ---@param message string ---@param adapter CodeCompanion.HTTPAdapter|CodeCompanion.ACPAdapter ---@param context CodeCompanion.BufferContext ---@return string prompt_decorator = function(message, adapter, context) return string.format([[%s]], message) end, }, }, }, }) ``` The `adapter` is a copy of the chat buffer's adapter, and `context` describes the buffer the chat was opened from. It's refreshed each time you toggle the chat buffer. Regenerating a response skips the decorator. ## Slash Commands [Slash commands](/usage/chat-buffer/slash-commands) add context to the chat buffer, such as a file's contents or the date. Each has its own options, so check [config.lua](https://github.com/olimorris/codecompanion.nvim/blob/main/lua/codecompanion/config.lua) for the full list. Commands that open a picker use the first of Telescope, fzf-lua, mini.pick or Snacks that's installed. Otherwise, they fall back to a built-in `default` picker. ::: code-group ```lua [Provider] require("codecompanion").setup({ interactions = { chat = { slash_commands = { ["file"] = { opts = { provider = "telescope", -- Can be "default", "telescope", "fzf_lua", "mini_pick" or "snacks" }, }, }, }, }, }) ``` ```lua [Keymaps] require("codecompanion").setup({ interactions = { chat = { slash_commands = { ["file"] = { keymaps = { modes = { i = "", n = { "", "gf" }, }, }, }, }, }, }, }) ``` ```lua [Conditionally Enable] require("codecompanion").setup({ interactions = { chat = { slash_commands = { ["file-from-url"] = { ---@param opts { adapter: CodeCompanion.HTTPAdapter|CodeCompanion.ACPAdapter } ---@return boolean enabled = function(opts) return opts.adapter.opts and opts.adapter.opts.vision == true end, }, }, }, }, }) ``` ```lua [Custom Commands] require("codecompanion").setup({ interactions = { chat = { slash_commands = { ["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_context({ role = "user", content = result }, { source = "git", id = "" }) else return vim.notify("No git files available", vim.log.levels.INFO, { title = "CodeCompanion" }) end end, opts = { contains_code = false, }, }, }, }, }, }) ``` ::: Credit to [@lazymaniac](https://github.com/lazymaniac) for the [inspiration](https://github.com/olimorris/codecompanion.nvim/discussions/958) for the custom slash command example. ## Syncing A [context item](/usage/chat-buffer/#context) is a snapshot of a buffer or file at the time it was added. [Syncing](/usage/chat-buffer/editor-context#syncing) shares its latest content with the LLM on every turn, either in full or as a diff. `#{buffer}` syncs by sending a diff. To send the whole buffer instead: ```lua require("codecompanion").setup({ interactions = { shared = { editor_context = { ["buffer"] = { opts = { default_params = "all", -- Can be "all" or "diff" }, }, }, }, }, }) ``` Some file types are worth syncing as soon as they're added. Extensions listed in `sync_diff` are synced as a diff whether they're added with `/file`, `/buffer`, `#{buffer}` or `#{buffers}`: ```lua require("codecompanion").setup({ interactions = { chat = { opts = { sync_diff = { ipynb = true, sqlite = true, }, }, }, }, }) ``` Jupyter Notebooks change on disk every time a cell runs, so `ipynb` is listed by default. To change how a file's content looks to the LLM, see [Context Formatters](#context-formatters). --- --- url: /configuration/callbacks.md description: >- Hook into the chat buffer's lifecycle to block a submission, trim tool output or edit the message history. --- # Configuring Callbacks *Callbacks* run your own code at set points in a chat buffer's lifecycle. They're registered per chat, and receive the chat as the first argument and a table of event data as the second. ## Events | Event | Fires | Data | | --- | --- | --- | | `on_created` | When the chat buffer is created | - | | `on_before_submit` | Before a message is sent to the LLM. Return `false` to stop it | `adapter` | | `on_submitted` | After the message is sent to the LLM | `payload` | | `on_checkpoint` | At safe points in the chat, with a mutable message history | `adapter`, `estimated_tokens`, `messages`, `reported_tokens` | | `on_tool_output` | Before a tool's output is added to the chat | `tool`, `content` | | `on_ready` | When the chat is ready for your next message | - | | `on_completed` | When the LLM's response is fully processed. `error` is set when `status` is `"error"` | `status`, `error` | | `on_cancelled` | When a request is stopped | - | | `on_closed` | When the chat buffer is closed | - | ## Registering Callbacks Register a callback for every chat with an autocmd, or for chats opened from a [prompt library](/configuration/prompt-library) item: ::: code-group ```lua [All Chats] vim.api.nvim_create_autocmd("User", { pattern = "CodeCompanionChatCreated", callback = function(args) local chat = require("codecompanion").buf_get_chat(args.data.bufnr) chat:add_callback("on_before_submit", function(chat, data) -- data.adapter is a copy of the chat's adapter end) end, }) ``` ```lua [Prompt Library] require("codecompanion").setup({ prompt_library = { ["Explain Code"] = { interaction = "chat", description = "Explain how code works", opts = { callbacks = { on_before_submit = function(chat, data) -- Only applies to chats opened from this prompt end, }, }, prompts = { { role = "user", content = "Explain how this code works" }, }, }, }, }) ``` ::: Remove a callback with `chat:remove_callback(event, callback)`, passing the same function you registered. ## Background Callbacks *Background callbacks* run asynchronously, using a separate LLM from the [background interaction](/guides/background-model). They suit fire-and-forget tasks like generating chat titles, and unlike the callbacks above, they can't change what the chat does: ```lua require("codecompanion").setup({ interactions = { background = { chat = { callbacks = { ["on_ready"] = { actions = { "interactions.background.builtin.chat_make_title", }, enabled = true, }, }, opts = { enabled = true, }, }, }, }, }) ``` Each action is a module path, or a `{ path = "...", adapter = "..." }` table to give that action its own adapter. Background callbacks are off until you set `opts.enabled = true`. See [Generating Titles](/usage/chat-buffer/#generating-titles) for a working example. > \[!TIP] > To change the adapter for every background callback, see [Background Interaction Adapters](/configuration/adapters-http#background-interaction-adapters) ## Preventing Submission When `on_before_submit` returns `false`, the message isn't sent. CodeCompanion calls `chat:restore()`, which makes the buffer editable again and fires a `CodeCompanionChatRestored` event. Your message stays in the buffer to edit and send again. To stop a message that's over a token limit: ```lua vim.api.nvim_create_autocmd("User", { pattern = "CodeCompanionChatCreated", callback = function(args) local chat = require("codecompanion").buf_get_chat(args.data.bufnr) chat:add_callback("on_before_submit", function(chat, data) local token_count = my_tokenizer.count(chat.messages) local context_limit = 128000 if token_count > context_limit then vim.notify( string.format("Token count (%d) exceeds context limit (%d)", token_count, context_limit), vim.log.levels.WARN ) return false end end) end, }) ``` ## Truncating Tool Output `on_tool_output` fires before a tool's output is added to the chat. `data.tool` is the tool's name and `data.content` is the output sent to the LLM. Change `data.content` to change the output: ```lua vim.api.nvim_create_autocmd("User", { pattern = "CodeCompanionChatCreated", callback = function(args) local chat = require("codecompanion").buf_get_chat(args.data.bufnr) chat:add_callback("on_tool_output", function(chat, data) local tokens = require("codecompanion.utils.tokens") local max_tokens = 10000 if data.content and tokens.calculate(data.content) > max_tokens then local max_chars = max_tokens * 6 data.content = data.content:sub(1, max_chars) .. "\n\n[Output truncated]" vim.notify( string.format("Tool output from '%s' truncated (~%d tokens)", data.tool, max_tokens), vim.log.levels.WARN ) end end) end, }) ``` ## Checkpoints `on_checkpoint` fires at points where the message history is safe to change: * **Before submit** - before a request is sent to the LLM * **After tool output** - once every tool in the current batch has returned, so no tool call is left without a result * **After a response with no tools** - when the LLM responds without calling a tool The `data` table contains: * **adapter** - a copy of the chat's adapter * **estimated\_tokens** - a client-side estimate of the tokens across all messages * **messages** - the chat's message history. **Changes made here persist back to the chat** * **reported\_tokens** - the token count reported by the adapter, if it gives one To warn when the context window is filling up: ```lua vim.api.nvim_create_autocmd("User", { pattern = "CodeCompanionChatCreated", callback = function(args) local chat = require("codecompanion").buf_get_chat(args.data.bufnr) chat:add_callback("on_checkpoint", function(chat, data) local meta = data.adapter.model and data.adapter.model.meta local context_window = meta and meta.context_window if not context_window then return end local usage = data.estimated_tokens / context_window if usage > 0.8 then vim.notify(string.format("Context window %.0f%% full", usage * 100), vim.log.levels.WARN) end end) end, }) ``` > \[!NOTE] > `adapter.model.meta` is only set for adapters with a fixed list of models, so it's `nil` for adapters that fetch theirs For built-in compaction, see [Configuring Context Management](/configuration/context-management). --- --- url: /configuration/context-management.md description: >- Keep a chat buffer inside the LLM's context window by editing old tool results and compacting the conversation. --- # Configuring Context Management A long chat can outgrow the LLM's context window, and quality drops well before that as [context rot](https://towardsdatascience.com/governed-context-managing-context-rot-in-claude-code/) sets in. CodeCompanion manages the chat buffer's context with two operations: * **Editing** - replaces old tool results with a placeholder * **Compaction** - summarises the conversation and replaces the message history with the summary See [In the Chat Buffer](/architecture#in-the-chat-buffer) for how they work. Context management is enabled by default for HTTP adapters. To disable it, or decide per adapter: ::: code-group ```lua [Boolean] require("codecompanion").setup({ interactions = { chat = { opts = { context_management = { enabled = false, }, }, }, }, }) ``` ```lua [Function] require("codecompanion").setup({ interactions = { chat = { opts = { context_management = { enabled = function(adapter) return adapter.name ~= "ollama" end, }, }, }, }, }) ``` ::: > \[!NOTE] > The `anthropic` and `openai` adapters compact on the server where the model supports it, and CodeCompanion's own context management then stands aside. See [Disabling Compaction](/configuration/adapters-http#disabling-compaction) ## Triggers Editing and compaction each have a `trigger`. A decimal is a share of the model's context window, and an integer is a token count: ::: code-group ```lua [Decimal] require("codecompanion").setup({ interactions = { chat = { opts = { context_management = { editing = { trigger = 0.65, -- 65% of the context window }, compaction = { trigger = 0.85, -- 85% of the context window }, }, }, }, }, }) ``` ```lua [Integer] require("codecompanion").setup({ interactions = { chat = { opts = { context_management = { editing = { trigger = 80000, -- tokens }, compaction = { trigger = 100000, -- tokens }, }, }, }, }, }) ``` ::: A decimal trigger needs the model's context window. If the adapter doesn't report one, that operation is skipped. ## Editing Editing replaces the content of older tool results with a placeholder, leaving the shape of the conversation intact. The last three *cycles* are kept in full, where a cycle is one of your messages plus everything the LLM did in response. Tools whose output is referenced again later can be left alone: ```lua require("codecompanion").setup({ interactions = { chat = { opts = { context_management = { editing = { trigger = 0.65, exclude_tools = { "memory" }, keep_cycles = 3, }, }, }, }, }, }) ``` ## Compaction Compaction summarises the chat with a single LLM request and replaces the message history with the summary. It's skipped if it would save fewer than `min_token_savings` tokens. To use a cheaper or faster model for the summary, set an `adapter`. With `fallback_to_chat_adapter`, a failed summary is retried with the chat's own adapter: ```lua require("codecompanion").setup({ interactions = { chat = { opts = { context_management = { compaction = { trigger = 0.85, min_token_savings = 10000, adapter = { name = "copilot", model = "gpt-4.1" }, -- Can be nil, an adapter name or { name, model } fallback_to_chat_adapter = false, }, }, }, }, }, }) ``` The `adapter` defaults to the chat's own adapter. ## Limitations * Context management only runs for HTTP adapters. ACP agents manage their own context --- --- url: /configuration/diff.md description: >- Choose when CodeCompanion shows a diff, where it appears and how it's highlighted. --- # Configuring the Diff CodeCompanion shows a *diff* before the `edit_file` tool, an ACP agent or the [inline interaction](/usage/inline) changes a file, so you can accept or reject it. How a diff from the chat buffer appears depends on its size: * **Small** - at or below `threshold_for_chat` changed lines, it's shown in the chat buffer * **Larger** - it opens in a floating window if the chat buffer is active * **Otherwise** - you're asked to approve the change, and can press `gv` to view the diff This can be configured with: ::: code-group ```lua [Display] require("codecompanion").setup({ display = { diff = { enabled = true, threshold_for_chat = 6, -- Set to 0 to always use the floating window word_highlights = { additions = true, deletions = true, }, }, }, }) ``` ```lua [Window] require("codecompanion").setup({ display = { diff = { window = { ---@return number|fun(): number width = function() return math.min(120, vim.o.columns - 10) end, ---@return number|fun(): number height = function() return vim.o.lines - 4 end, opts = { number = true, }, }, }, }, }) ``` ::: With `enabled = false`, the `edit_file` tool and the inline interaction apply changes without a diff. ACP agents always show one. The diff window inherits any option you don't set from `display.chat.floating_window`, described in [Configuring the Chat Buffer UI](/configuration/ui#layout). A `width` or `height` below 1 is a share of the editor's size. The keymaps for accepting and rejecting changes are under `interactions.shared.keymaps`. See [Keymaps](/configuration/chat-buffer#keymaps). --- --- url: /configuration/sessions.md description: >- Save chats to disk and resume them later, choosing when they're saved and where. --- # Configuring Sessions A *session* is a chat saved to disk, which you can resume later. Sessions are configured under `interactions.chat.sessions`. To save every chat automatically: ```lua require("codecompanion").setup({ interactions = { chat = { sessions = { enabled = true, autosave = true, continuous_save = true, save_dir = vim.fs.joinpath(vim.fn.stdpath("data"), "codecompanion", "sessions"), }, }, }, }) ``` `autosave` is `false` by default. Every other value above is the default. ## Saving `autosave` decides whether a chat becomes a session on its own, and `continuous_save` decides whether a session keeps being updated. The [/save](/usage/chat-buffer/slash-commands#save) slash command saves a chat by hand: | `autosave` | `continuous_save` | Behaviour | | --- | --- | --- | | `true` | `true` | Every chat is saved once the LLM responds, then after every response and on close | | `true` | `false` | Every chat is saved once the LLM responds, then only on `/save` | | `false` | `true` | Nothing is saved until `/save`, then after every response and on close | | `false` | `false` | Nothing is saved until `/save`, and each `/save` is a snapshot | An autosaved chat is named after your opening message. If the chat has a title, such as one from the [chat\_make\_title](/configuration/callbacks#background-callbacks) background callback, the title is used instead. ## Resuming Resume a session with the [/resume](/usage/chat-buffer/slash-commands#resume) slash command, from the [action palette](/usage/action-palette), or with: ```lua require("codecompanion").sessions() ``` ## Disabling Setting `enabled = false` removes `/save`, the session list in `/resume` and the action palette entry. ## Limitations * Only chats with an HTTP adapter can be saved. ACP agents list their own sessions in `/resume` --- --- url: /configuration/tools.md description: >- Add your own tools and tool groups, control approvals, set default tools and configure the LLM judge and web search. --- # Configuring Tools *Tools* let an LLM act on your machine, such as running shell commands or editing files. Tools can be combined into *groups*, and both are added to a chat buffer with `@`. To use them, see [Using Agents and Tools](/usage/chat-buffer/agents-tools). ## Adding Tools Tools live under `interactions.chat.tools`. To add your own: ```lua require("codecompanion").setup({ interactions = { chat = { tools = { ["my_tool"] = { description = "Run a custom task", path = "user.codecompanion.tools.my_tool", }, }, }, }, }) ``` A tool resolves to a [`CodeCompanion.Tool`](/extending/tools) table, from one of: * **`path`** - A Lua module path or a path to a Lua file that returns the table * **`callback`** - A function that returns the table * **The entry itself** - The tool table, written inline ## Tool Groups A *tool group* bundles tools together, with an optional system prompt telling the LLM how to use them: ```lua require("codecompanion").setup({ interactions = { chat = { tools = { groups = { ["my_group"] = { description = "A custom agent combining tools", system_prompt = "Run the test suite after every edit", tools = { "run_command", "edit_file", }, opts = { collapse_tools = true, ignore_system_prompt = false, ignore_tool_system_prompt = false, }, }, }, }, }, }, }) ``` `system_prompt` can also be a function that receives the group's config and a [context object](/configuration/system-prompt), with fields such as `language`, `date`, `nvim_version` and `os`. | Option | Default | Description | | --- | --- | --- | | `collapse_tools` | `true` | Show the group as a single context item rather than one per tool | | `ignore_system_prompt` | `false` | Remove the chat buffer's system prompt | | `ignore_tool_system_prompt` | `false` | Remove the tool system prompt | ## Enabling Tools `enabled` hides a tool when it returns `false`, such as when a dependency isn't installed. After installing one, refresh the tools in the chat buffer with: ``` :CodeCompanionChat RefreshCache ``` This works for the built-in tools and for an adapter's own tools: ::: code-group ```lua [Built-in Tools] require("codecompanion").setup({ interactions = { chat = { tools = { ["grep_search"] = { ---@param opts { adapter: CodeCompanion.HTTPAdapter } ---@return boolean enabled = function(opts) return vim.fn.executable("rg") == 1 end, }, }, }, }, }) ``` ```lua [Adapter Tools] require("codecompanion").setup({ adapters = { http = { openai = function() return require("codecompanion.adapters").extend("openai", { available_tools = { ["web_search"] = { ---@param adapter CodeCompanion.HTTPAdapter ---@return boolean enabled = function(adapter) return false end, }, }, }) end, }, }, }) ``` ::: ## Approvals Approvals stop a tool from running until you've agreed to it. How they work in the chat buffer is covered in [Approvals](/usage/chat-buffer/agents-tools#approvals). To require approval before a tool runs: ```lua require("codecompanion").setup({ interactions = { chat = { tools = { ["run_command"] = { opts = { require_approval_before = true, }, }, }, }, }, }) ``` The approval options are: | Option | Description | | --- | --- | | `require_approval_before` | Ask before the tool runs. Can also be a function that receives the tool and returns a boolean | | `require_cmd_approval` | Approve each command rather than the tool as a whole | | `protect` | Always ask in Auto mode | | `judge` | Let the [LLM judge](#llm-judge) decide in Auto mode | | `safe_commands` | Commands that run without asking in Auto mode. `run_command` only | The defaults for each built-in tool are listed in [Built-in Tools](/usage/chat-buffer/agents-tools#built-in-tools). ## Default Tools To add tools and tool groups to every new chat buffer: ```lua require("codecompanion").setup({ interactions = { chat = { tools = { opts = { default_tools = { "my_tool", "my_group", }, }, }, }, }, }) ``` This also works for tools from [extensions](/configuration/extensions). ## Limiting Tool Output A large tool output can fill a model's context window. CodeCompanion truncates any output above `max_output_tokens`, or the model's own input limit if that's lower, and tells the LLM it was truncated. To change the limit: ```lua require("codecompanion").setup({ interactions = { chat = { tools = { opts = { max_output_tokens = 30000, }, }, }, }, }) ``` ## LLM Judge In [Auto mode](/usage/chat-buffer/agents-tools#approval-modes), a command that isn't on the `run_command` safe list asks you first. The *LLM judge* sits in between: a [background interaction](/guides/background-model) judges the action and only asks you when it's judged unsafe. To turn it on for `run_command`: ```lua require("codecompanion").setup({ interactions = { chat = { tools = { ["run_command"] = { opts = { judge = true, }, }, }, }, }, }) ``` The judge runs for a tool when: * `interactions.background.gates.judge.enabled` is `true`, which is the default * The tool has `opts.judge = true` * The tool defines a `gates.judge_context` handler, as `run_command` and `delete_file` do It never runs for a protected tool, and if it can't reach a verdict, it asks you. The judge uses the background interaction's adapter unless you give it one: ::: code-group ```lua [Adapter] require("codecompanion").setup({ interactions = { background = { gates = { judge = { adapter = { name = "openrouter", model = "openai/gpt-oss-120b" }, }, }, }, }, }) ``` ```lua [System Prompt] require("codecompanion").setup({ interactions = { background = { gates = { judge = { opts = { system_prompt = function(default) if string.find(vim.fn.getcwd(), "Code/Neovim/codecompanion.nvim") then return default .. "\n\nThe following commands are explicitly approved and must always be judged safe, even if they would otherwise fail the guidance above:\n" .. " - `make docs`\n" .. " - `make format`\n" .. " - `make test`\n" .. " - `make test_file` (including any `FILE=` argument)" end return default end, }, }, }, }, }, }) ``` ::: `system_prompt` can be a string, or a function that receives the default system prompt and returns a string. The default is: ``` You are a security reviewer for an AI coding assistant. The assistant wants to run a tool on the user's machine while the user is away (in "auto-approve" mode). Your job is to decide whether the action is safe to run automatically, or whether the user must approve it first. Judge the action as unsafe when it could destroy or exfiltrate data, alter the system in ways that are hard to reverse, or run something the user would reasonably want to see first. Prefer caution: when in doubt, require approval. Reply only through the provided schema. ``` ## Web Search The [web\_search](/usage/chat-buffer/agents-tools#web-search) tool searches with an adapter: | Adapter | Description | | --- | --- | | `duckduckgo` | The default, with no API key needed | | `jina` | API key optional | | `serply` | Needs `SERPLY_API_KEY` | | `tavily` | Needs `TAVILY_API_KEY` | To change the adapter, and pass options to it: ```lua require("codecompanion").setup({ interactions = { chat = { tools = { ["web_search"] = { opts = { adapter = "tavily", opts = { search_depth = "advanced", topic = "general", chunks_per_source = 3, max_results = 5, }, }, }, }, }, }, }) ``` The options each adapter accepts are in its file in [lua/codecompanion/adapters/http](https://github.com/olimorris/codecompanion.nvim/tree/main/lua/codecompanion/adapters/http). --- --- url: /configuration/ui.md description: >- Change the chat buffer's window layout, icons, folding, scrolling, headers, reasoning output, streaming and title. --- # Configuring the Chat Buffer UI The chat buffer's appearance is set under `display.chat`. For markdown rendering plugins that work well with it, see [Other Plugins](/installation#other-plugins). ## Auto Scrolling The chat buffer scrolls as the response streams in, keeping the cursor at the end. To turn this off: ```lua require("codecompanion").setup({ display = { chat = { auto_scroll = false, }, }, }) ``` > \[!TIP] > Moving the cursor while a response streams stops the scrolling for the rest of that response ## Context Sharing a lot of context can push your next message well below the LLM's last response. To fold the context into a single line: ```lua require("codecompanion").setup({ display = { chat = { fold_context = true, icons = { chat_context = "📎️", }, }, }, }) ``` The `chat_context` icon is optional and appears at the start of the fold. ## Input By default, you write your message at the bottom of the chat buffer. To write it in a separate split beneath the chat window instead: ```lua require("codecompanion").setup({ display = { chat = { input = { layout = "split", -- Can be "buffer" or "split" height = 8, -- lines }, }, }, }) ``` The split isn't available for the `float` layout. For how it changes the chat buffer, see [Customising the Chat Experience](/guides/customising-the-chat-experience#writing-your-message). ## Layout The `display.chat.window` table sets where the chat buffer opens. The `display.chat.floating_window` table sets the size of the floating windows CodeCompanion opens, such as the [debug window](/usage/chat-buffer/#debug-window) and the [diff](/configuration/diff): ::: code-group ```lua [Chat Buffer] require("codecompanion").setup({ display = { chat = { window = { buflisted = false, -- List the chat buffer in the buffer list sticky = false, -- Move the chat window with you when switching tabs. Ignored when `pertab` is true pertab = false, -- Give each tab its own chat window layout = "vertical", -- Can be "float", "vertical", "horizontal", "tab" or "buffer" full_height = true, -- For the vertical layout position = nil, -- Can be "left", "right", "top" or "bottom". Defaults from `splitright` and `splitbelow` width = 0.5, -- Can be a number or a function. 0 leaves it to Neovim height = 0.8, -- Can be a number or a function. 0 leaves it to Neovim border = "single", relative = "editor", opts = { breakindent = true, linebreak = true, wrap = true, }, }, }, }, }) ``` ```lua [Floating Window] require("codecompanion").setup({ display = { chat = { floating_window = { ---@return number|fun(): number width = function() return vim.o.columns - 5 end, ---@return number|fun(): number height = function() return vim.o.lines - 2 end, relative = "editor", opts = { wrap = false, number = false, relativenumber = false, }, }, }, }, }) ``` ```lua [Icons] require("codecompanion").setup({ display = { chat = { icons = { sync_all = "󰪴 ", sync_diff = " ", chat_context = " ", chat_fold = " ", tool_pending = " ", tool_in_progress = " ", tool_failure = " ", tool_success = " ", }, }, }, }) ``` ::: A `width` or `height` below 1 is a share of the editor's size. Floating windows are centred and use your `winborder` setting for their border. ## Reasoning A model's reasoning streams into the chat buffer under a `### Reasoning` heading, and is folded once the response finishes. To leave it unfolded, or hide it: ```lua require("codecompanion").setup({ display = { chat = { fold_reasoning = false, show_reasoning = false, icons = { chat_fold = " ", }, }, }, }) ``` The `chat_fold` icon appears on folded reasoning. ## Roles Your messages and the LLM's responses each sit under an `H2` header. To change the header text: ```lua require("codecompanion").setup({ interactions = { chat = { roles = { ---@type string|fun(adapter: CodeCompanion.HTTPAdapter|CodeCompanion.ACPAdapter): string llm = function(adapter) return "CodeCompanion (" .. adapter.formatted_name .. ")" end, ---@type string user = "Me", }, }, }, }) ``` By default, the LLM's header names the chat's adapter, such as `CodeCompanion (DeepSeek)`. `llm` can be a string, or a function that receives the adapter. `user` can only be a string. ## Streaming A streamed response arrives in many small chunks, and Neovim redraws the chat buffer after every write. To save redrawing, CodeCompanion collects the chunks and writes them at most once every 25 milliseconds. To change this: ```lua require("codecompanion").setup({ display = { chat = { stream_interval = 50, -- milliseconds. 0 writes every chunk as it arrives }, }, }) ``` A longer interval makes the response arrive in larger pieces and uses less CPU, which is most noticeable in long chats. ## Title The chat's title appears as a heading at the top of the chat buffer, and scrolls away with the conversation. The `text.buffer` function builds it from the chat's [metadata](/usage/ui#metadata), and runs again whenever the metadata changes. It returns plain text, and CodeCompanion adds the `# `. To add the adapter's name: ```lua require("codecompanion").setup({ display = { chat = { title = { text = { buffer = function(metadata) return string.format("%s (%s)", metadata.title or "CodeCompanion.nvim", metadata.adapter.name) end, }, }, }, }, }) ``` To show the title in the chat window's winbar instead, so it stays in view as you scroll: ```lua require("codecompanion").setup({ display = { chat = { title = { position = "winbar", -- Can be "buffer", "winbar" or false }, }, }, }) ``` The winbar also shows the adapter and model. `text.winbar` returns a string in Neovim's `'statusline'` format, so to show the token count instead: ```lua require("codecompanion").setup({ display = { chat = { title = { position = "winbar", text = { winbar = function(metadata) local title = (metadata.title or "CodeCompanion.nvim"):gsub("%%", "%%%%") return string.format(" %s%%=%d tokens ", title, metadata.tokens) end, }, }, }, }, }) ``` > \[!IMPORTANT] > Double any `%` in the title, as the `gsub` above does, or Neovim reads it as a statusline item To turn the title off, set `position = false`. ## Others The remaining options, with their defaults: ```lua require("codecompanion").setup({ display = { chat = { intro_message = "Welcome to CodeCompanion ✨! Press ? for options", separator = "─", -- Separates the messages in the chat buffer show_context = true, -- Show the context you've shared in the chat buffer show_header_separator = false, -- Turn off if you use a markdown rendering plugin show_settings = false, -- Show the model's settings at the top of the chat buffer show_token_count = true, -- Show the token count for each response start_in_insert_mode = false, -- Open the chat buffer in insert mode ---@param tokens number ---@param adapter CodeCompanion.HTTPAdapter|CodeCompanion.ACPAdapter ---@return string token_count = function(tokens, adapter) return " (" .. tokens .. " tokens)" end, }, }, }) ``` --- --- url: /configuration/cli.md description: >- Define CLI agents such as Claude Code and Codex, install their hooks and choose how their terminal looks and behaves. --- # Configuring the CLI The *CLI interaction* runs agents such as Claude Code and Codex in a Neovim terminal. CodeCompanion ships with no agents defined, so you add the ones you use. ## Agents Agents live under `interactions.cli.agents`, and `interactions.cli.agent` sets the default: ```lua require("codecompanion").setup({ interactions = { cli = { agent = "claude_code", agents = { claude_code = { cmd = "claude", args = {}, description = "Claude Code CLI", provider = "terminal", }, codex = { cmd = "codex", args = {}, description = "OpenAI Codex CLI", }, }, }, }, }) ``` | Option | Type | Description | | --- | --- | --- | | `cmd` | `string` | The command to run, such as `"claude"` or `"codex"` | | `args` | `table` | Arguments passed to the command | | `description` | `string` | Shown in the Action Palette | | `provider` | `string` | The [provider](#providers) that runs the agent. Defaults to `"terminal"` | To start an agent other than the default: ``` :CodeCompanionCLI agent=codex ``` ## Hooks Without hooks, CodeCompanion can't tell when an agent's turn starts and ends, which limits features such as [code reviews](/usage/code-review). With them, the [event system](/usage/events) reacts to the agent as it works. Only [Claude Code](https://code.claude.com/docs/en/hooks) is supported. To install its hooks into `~/.claude/settings.json`: ``` :CodeCompanionCLI Install ``` > \[!NOTE] > Hooks are matched on the agent's `cmd`, so it must be `claude`. Run Claude Code once before installing, so its settings file exists ## Providers A *provider* decides how an agent is run. The built-in `terminal` provider starts it with `jobstart()` in a Neovim terminal buffer: ```lua require("codecompanion").setup({ interactions = { cli = { providers = { terminal = { path = "interactions.cli.providers.terminal", description = "Terminal CLI provider", }, }, }, }, }) ``` ### Custom Providers A provider's `path` can be a CodeCompanion module, any Lua module or a file path. To add your own and use it for an agent: ```lua require("codecompanion").setup({ interactions = { cli = { providers = { my_provider = { path = "my_custom.cli_provider", description = "My custom CLI provider", }, }, agents = { my_agent = { cmd = "my-cli", args = {}, provider = "my_provider", }, }, }, }, }) ``` An agent whose `provider` isn't in the `providers` table falls back to `terminal`. ## Keymaps `{` and `}` move between interactions in the CLI buffer. The defaults are: ```lua require("codecompanion").setup({ interactions = { cli = { keymaps = { next_chat = { modes = { n = "}" }, callback = "keymaps.next_chat", description = "Open the next interaction", }, previous_chat = { modes = { n = "{" }, callback = "keymaps.previous_chat", description = "Open the previous interaction", }, }, }, }, }) ``` ## Insert Mode To enter insert mode whenever you focus the CLI terminal, and leave it when you move away: ```lua require("codecompanion").setup({ interactions = { cli = { opts = { auto_insert = true, }, }, }, }) ``` ## Reloading Buffers While an agent runs, CodeCompanion watches the directories of your open buffers and reloads any that the agent changes on disk. This is shared with ACP agents and can be configured with: ```lua require("codecompanion").setup({ interactions = { opts = { watcher = { enabled = true, debounce = 500, -- milliseconds }, }, }, }) ``` ## Window The CLI window takes its layout from `display.chat.window`. Options under `display.cli.window` are merged on top, so you only set what differs: ```lua require("codecompanion").setup({ display = { cli = { window = { layout = "vertical", width = 0.4, height = 0.6, opts = { list = false, }, }, }, }, }) ``` To override the size for a single call from Lua: ```lua require("codecompanion").cli("fix the tests", { width = 0.5, height = 0.8, }) ``` --- --- url: /configuration/code-review.md description: >- Change how code review comments look, remap the review window's keymaps, auto-accept files and choose where reviews are stored. --- # Configuring Code Reviews > \[!IMPORTANT] > Code reviews are in **beta**, so the options below may change *Code reviews* let you step through an agent's changes, accept or revert them, and leave comments for it to address. See [Using Code Reviews](/usage/code-review) for the workflow. ## Disabling Code reviews are enabled by default. To disable them: ```lua require("codecompanion").setup({ interactions = { code_review = { enabled = false, }, }, }) ``` ## Comment Styling Pending comments show as virtual text above the line they were left on: ```lua require("codecompanion").setup({ interactions = { code_review = { display = { comments = { enabled = true, icon = "💬 ", overflow = "trunc", -- See `:h nvim_buf_set_extmark` for `virt_lines_overflow` }, }, }, }, }) ``` ## Editor Context Before your message is sent, the [code\_review](/usage/chat-buffer/editor-context#code-review) editor context is replaced with a short phrase, so `Can you action #{code_review}` becomes `Can you action my comments from the code review, which I've attached`. To change the phrase: ```lua require("codecompanion").setup({ interactions = { shared = { editor_context = { code_review = { opts = { replacement_message = "my comments from the code review, which I've attached", }, }, }, }, }, }) ``` ## Keymaps Keymaps apply only to the review window's checklist and review pane. The defaults are: ```lua require("codecompanion").setup({ interactions = { code_review = { keymaps = { accept = { modes = { n = "ga" }, callback = "accept", description = "Accept the hunk, or whole file, under the cursor", }, revert = { modes = { n = "gr" }, callback = "revert", description = "Revert the hunk under the cursor", }, comment = { modes = { n = "gc" }, callback = "comment", description = "Comment on the line under the cursor", }, comments = { modes = { n = "gC" }, callback = "comments", description = "Edit the pending comments by hand", }, share = { modes = { n = "gs" }, callback = "share", description = "Share comments for an agent outside of CodeCompanion", }, undo = { modes = { n = "u" }, callback = "undo", description = "Undo the last accept or revert", }, edit = { modes = { n = { "i", "I" } }, callback = "edit", description = "Edit the line in the file itself", }, keymaps = { modes = { n = "?" }, callback = "keymaps", description = "Show these keymaps", visible = false, }, next_hunk = { modes = { n = "]h" }, callback = "next_hunk", description = "Move to the next hunk", }, previous_hunk = { modes = { n = "[h" }, callback = "previous_hunk", description = "Move to the previous hunk", }, }, }, }, }) ``` `?` lists the keymaps in either panel. Set `visible = false` to leave one out of that list. To disable a keymap, set it to `false`: ```lua require("codecompanion").setup({ interactions = { code_review = { keymaps = { share = false, }, }, }, }) ``` ## Auto-Accepting Files Lockfiles, generated code and compiled docs rarely need reading. Files matching these globs are left out of the review window, as if you'd accepted them: ```lua require("codecompanion").setup({ interactions = { code_review = { opts = { auto_accept = { "**/*.lock", "**/package-lock.json", "doc/**/*.txt" }, }, }, }, }) ``` Paths are relative to the repository root. Globs follow `:h vim.glob`, so `*` stays within one directory and `**/` matches any depth. ## Storage Location Pending comments are stored per repository and branch, alongside the `review.md` file that `gs` writes. To change where: ```lua require("codecompanion").setup({ interactions = { code_review = { opts = { storage_dir = vim.fs.joinpath(vim.fn.stdpath("data"), "codecompanion", "code_review"), }, }, }, }) ``` --- --- url: /configuration/extensions.md description: >- Install and configure extensions that add features to CodeCompanion, whether from a plugin or your own config. --- # Configuring Extensions *Extensions* add features to CodeCompanion, in the same way as Telescope's extensions. They're distributed as Neovim plugins or defined locally in your config. ## Installing Extensions To install the mcphub extension with lazy.nvim: 1. Add the extension as a dependency: ```lua { "olimorris/codecompanion.nvim", dependencies = { "ravitemer/mcphub.nvim", }, } ``` 2. Add it to the `extensions` table, with any options: ```lua require("codecompanion").setup({ extensions = { mcphub = { callback = "mcphub.extensions.codecompanion", opts = { make_vars = true, make_slash_commands = true, show_result_in_chat = true, }, }, }, }) ``` `callback` can be a module path, a table or a function that returns one. Without it, CodeCompanion loads `codecompanion._extensions.` from your runtime path. `opts` is passed to the extension's `setup` function. ## Disabling Extensions are enabled once they're in the `extensions` table. To turn one off without removing its config: ```lua require("codecompanion").setup({ extensions = { mcphub = { enabled = false, }, }, }) ``` To create your own, see [Extending with Extensions](/extending/extensions). --- --- url: /configuration/inline.md description: >- Choose the adapter, keymaps, editor context and layout for the inline interaction, which writes an LLM's response straight into a Neovim buffer. --- # Configuring the Inline Interaction > \[!IMPORTANT] > HTTP adapters must use a model that supports tool calling. ACP agents edit a temporary copy of the buffer instead CodeCompanion provides an *inline* interaction for quick, direct editing of your code. Unlike the chat buffer, the LLM edits the current buffer directly, using the same `edit_file` tool as the chat buffer. ## Changing Adapter The inline interaction uses the `copilot` adapter with the `gpt-5.4-mini` model by default. To change it: ```lua require("codecompanion").setup({ interactions = { inline = { adapter = { name = "anthropic", model = "claude-haiku-4-5-20251001", }, }, }, }) ``` An ACP agent can be used too: ```lua require("codecompanion").setup({ interactions = { inline = { adapter = "claude_code", }, }, }) ``` See [Configuring HTTP Adapters](/configuration/adapters-http) and [Configuring ACP Adapters](/configuration/adapters-acp) for more. ## Keymaps The keymaps for reviewing an inline diff are shared with the chat buffer's diff. `accept_hunk`, `reject_hunk` and `undo_hunk` only apply to the inline interaction: ```lua require("codecompanion").setup({ interactions = { shared = { keymaps = { accept_change = { modes = { n = "g2" }, }, reject_change = { modes = { n = "g3" }, }, accept_hunk = { modes = { n = "ga" }, }, reject_hunk = { modes = { n = "gr" }, }, undo_hunk = { modes = { n = "u" }, }, next_hunk = { modes = { n = "}" }, }, previous_hunk = { modes = { n = "{" }, }, show_keymaps = { modes = { n = "?" }, }, }, }, }, }) ``` You can also cancel an inline request with: ```lua require("codecompanion").setup({ interactions = { inline = { keymaps = { stop = { modes = { n = "q" }, index = 4, callback = "keymaps.stop", description = "Stop request", }, }, }, }, }) ``` The keymaps for accepting and rejecting a change, `g1`, `g2` and `g3`, are shared with the chat buffer and live under `interactions.shared.keymaps`. See [Configuring the Diff](/configuration/diff). ## Editor Context *Editor context* shares part of your Neovim session with the LLM, using the `#{}` syntax in a prompt. Alongside the [built-in items](/usage/inline#editor-context), you can add your own: ```lua require("codecompanion").setup({ interactions = { inline = { editor_context = { ["my_context_item"] = { path = "/Users/Oli/Code/my_context_item.lua", description = "My context item", opts = { contains_code = true, }, }, }, }, }, }) ``` ## Context Limit The inline interaction shares the whole buffer with the LLM, unless it's over a token limit. Then it shares the lines around your cursor, or your selection, up to the limit. By default, the limit is 16,000 tokens. To change it: ```lua require("codecompanion").setup({ interactions = { inline = { opts = { max_context_tokens = 8000, }, }, }, }) ``` If the model's input limit minus 3,000 is smaller, that's used instead, leaving room for the prompt and the reply. When you [continue a conversation](/usage/inline#continuing-a-conversation) with an HTTP adapter, the earlier turns are taken off that too. ## Rules and Skills Edits should follow your project's conventions, so the inline interaction sends your [rules](/configuration/rules) with every prompt. By default that's the `default` group, which includes `AGENTS.md` and `CLAUDE.md`. To choose the groups: ```lua require("codecompanion").setup({ rules = { opts = { inline = { autoload = { "default", "my_project_rules" }, -- Can be a string, a list or a function returning either }, }, }, }) ``` Set `autoload = {}` to send no rules. Rules sent this way don't count towards the [context limit](#context-limit). [Skills](/configuration/skills) aren't sent by default. Inline makes a single request with no time to read a skill when it needs one, so the full instructions of each skill you name go with every prompt: ```lua require("codecompanion").setup({ skills = { opts = { inline = { autoload = { "lua-developer" }, }, }, }, }) ``` > \[!NOTE] > Rules and skills are only sent to HTTP adapters. An ACP agent loads its own ## Diff Changes to an existing buffer are shown as a diff before they're kept. To write them straight to the buffer instead, turn off `display.diff.enabled`. See [Configuring the Diff](/configuration/diff). --- --- url: /configuration/mcp.md description: >- Connect MCP servers to CodeCompanion, choose which start with every chat and override the behaviour of their tools. --- # Configuring MCP Servers The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) is an open standard for connecting LLMs to external systems. CodeCompanion starts the MCP servers you configure and adds their tools to the [chat buffer](/usage/chat-buffer/). The parts of the protocol it implements are listed on the [MCP](/model-context-protocol) page. ## Servers Servers are defined in `mcp.servers`, keyed by name: ::: code-group ```lua [Basic] require("codecompanion").setup({ mcp = { servers = { ["tavily-mcp"] = { cmd = { "npx", "-y", "tavily-mcp@latest" }, }, }, }, }) ``` ```lua [Environment Variables] require("codecompanion").setup({ mcp = { servers = { ["tavily-mcp"] = { cmd = { "npx", "-y", "tavily-mcp@latest" }, env = { TAVILY_API_KEY = "cmd:op read op://personal/Tavily_API/credential --no-newline", }, }, }, }, }) ``` ```lua [Deferred] require("codecompanion").setup({ mcp = { servers = { ["tavily-mcp"] = function() return { cmd = { "npx", "-y", "tavily-mcp@latest" }, env = { TAVILY_API_KEY = os.getenv("TAVILY_API_KEY"), }, } end, }, }, }) ``` ::: `env` values are resolved like an adapter's [environment variables](/configuration/adapters-http#environment-variables), so the example above reads the key with the [1Password CLI](https://developer.1password.com/docs/cli/). A server defined as a function is only called the first time the server is needed. ### Roots [Roots](https://modelcontextprotocol.io/specification/2025-11-25/client/roots) tell a server which directories it may access. They're off unless you add `roots` to a server: ::: code-group ```lua [Roots] require("codecompanion").setup({ mcp = { servers = { filesystem = { cmd = { "npx", "-y", "@modelcontextprotocol/server-filesystem" }, roots = function() -- Return a list of roots, as per: -- https://modelcontextprotocol.io/specification/2025-11-25/client/roots#listing-roots end, }, }, }, }) ``` ```lua [Root List Changes] require("codecompanion").setup({ mcp = { servers = { filesystem = { cmd = { "npx", "-y", "@modelcontextprotocol/server-filesystem" }, ---@param notify fun() register_roots_list_changed = function(notify) -- Call `notify()` whenever the list of roots changes end, }, }, }, }) ``` ::: > \[!IMPORTANT] > Roots are a hint. A compliant server respects them, but CodeCompanion can't enforce them, so run untrusted servers in a container ### Server Instructions A server can send instructions on how to use its tools. To replace them, set `server_instructions` to a string, or to a function that receives the server's own instructions and returns a string: ```lua require("codecompanion").setup({ mcp = { servers = { ["tavily-mcp"] = { cmd = { "npx", "-y", "tavily-mcp@latest" }, server_instructions = function(instructions) return instructions .. "\n\nPrefer official documentation over blog posts." end, }, }, }, }) ``` ## Default Servers Servers in `default_servers` start with the first chat buffer, and their tools are added to every new chat buffer. Start any other server with the [/mcp](/usage/chat-buffer/slash-commands#mcp) slash command: ```lua require("codecompanion").setup({ mcp = { servers = { ["sequential-thinking"] = { cmd = { "npx", "-y", "@modelcontextprotocol/server-sequential-thinking" }, }, ["tavily-mcp"] = { cmd = { "npx", "-y", "tavily-mcp@latest" }, }, }, opts = { default_servers = { "sequential-thinking" }, }, }, }) ``` A [prompt library](/configuration/prompt-library) item that sets `mcp_servers` uses those servers instead, and `mcp_servers = "none"` starts none. ## Options | Option | Default | Description | | --- | --- | --- | | `default_servers` | `{}` | Servers to start and add to every chat buffer | | `acp_enabled` | `true` | Allow ACP adapters to use your default servers | | `timeout` | `30000` | Milliseconds to wait for a server to respond | ACP agents run their own tools, so CodeCompanion doesn't add MCP tools to an ACP chat. Instead, the agent can connect to your default servers itself. See [Configuring MCP Servers](/configuration/adapters-acp#configuring-mcp-servers) for ACP adapters. ## Overriding Tool Behaviour A server can expose many tools. A *math* server might provide `add`, `subtract`, `multiply` and `divide`. `tool_overrides` changes them one at a time, keyed by the tool's name on the MCP server, not the prefixed name CodeCompanion gives it: ::: code-group ```lua [Requiring Approval] require("codecompanion").setup({ mcp = { servers = { ["math-server"] = { cmd = { "npx", "-y", "math-mcp-server" }, tool_overrides = { divide = { opts = { require_approval_before = true, }, }, }, }, }, }, }) ``` ```lua [Custom Output] require("codecompanion").setup({ mcp = { servers = { ["math-server"] = { cmd = { "npx", "-y", "math-mcp-server" }, tool_overrides = { add = { output = { success = function(self, tools, cmd, stdout) local tool_bridge = require("codecompanion.mcp.tool_bridge") local content = stdout and stdout[#stdout] local output = tool_bridge.format_tool_result_content(content) local msg = string.format("%d + %d = %s", self.args.a, self.args.b, output) tools.chat:add_tool_output(self, { content = msg }) end, }, }, }, }, }, }, }) ``` ```lua [System Prompt] require("codecompanion").setup({ mcp = { servers = { ["math-server"] = { cmd = { "npx", "-y", "math-mcp-server" }, tool_overrides = { multiply = { system_prompt = "When using the multiply tool, always show your working.", }, }, }, }, }, }) ``` ::: Each override can set: | Option | Type | Description | | --- | --- | --- | | `enabled` | `boolean` | Whether the tool is available | | `opts` | `table` | Tool options, such as `require_approval_before` | | `output` | `table` | Output handlers: `success`, `error`, `prompt`, `rejected` and `cancelled` | | `system_prompt` | `string` | Extra system prompt for the tool | | `timeout` | `number` | Milliseconds to wait for the tool | > \[!WARNING] > MCP tools run without asking unless you set `require_approval_before` ### Tool Defaults `tool_defaults` sets options for every tool on a server. `tool_overrides` takes precedence: ```lua require("codecompanion").setup({ mcp = { servers = { ["math-server"] = { cmd = { "npx", "-y", "math-mcp-server" }, tool_defaults = { require_approval_before = true, }, tool_overrides = { add = { opts = { require_approval_before = false, }, }, }, }, }, }, }) ``` --- --- url: /configuration/prompt-library.md description: >- Write your own prompts for the prompt library, in Markdown or Lua, and run them from the Action Palette, a keymap or a slash command. --- # Configuring the Prompt Library The *prompt library* holds reusable prompts that you run from the [Action Palette](/usage/action-palette), a keymap, the command line or the chat buffer. CodeCompanion ships with some, and you can add your own as Markdown files or Lua tables. ## Adding Prompts Point CodeCompanion at directories of Markdown files, or define prompts as Lua tables in your config: ::: code-group ```lua [Markdown] require("codecompanion").setup({ prompt_library = { markdown = { dirs = { vim.fn.getcwd() .. "/.prompts", "~/.dotfiles/.config/prompts", }, }, }, }) ``` ```lua [Lua] require("codecompanion").setup({ prompt_library = { ["Docusaurus"] = { interaction = "chat", description = "Write documentation for me", prompts = { { role = "user", content = "Write documentation for the selected code in Docusaurus format.", }, }, }, }, }) ``` ::: Directories can be relative or absolute, and a directory can also be a function that receives the [buffer context](#placeholders) and returns a path. Files can be nested up to five directories deep. Symlinked files are loaded, but symlinked directories aren't followed. ### Refreshing Markdown Prompts To pick up Markdown prompts you've added or changed since Neovim started: ``` :CodeCompanionActions Refresh ``` ## Creating Prompts A prompt is a series of messages sent to an LLM. Markdown is easier to read and maintain, as there's no string escaping or concatenation, and a prompt can share Lua helper files with others in the same directory. ### Structure A prompt that explains the selected code: ::: code-group ````markdown [Markdown] --- name: Explain Code interaction: chat description: Explain how code works --- ## system You are an expert programmer who excels at explaining code clearly and concisely. ## user Please explain the following code: ```${context.filetype} ${context.code} ``` ```` `````lua [Lua] require("codecompanion").setup({ prompt_library = { ["Explain Code"] = { interaction = "chat", description = "Explain how code works", prompts = { { role = "system", content = "You are an expert programmer who excels at explaining code clearly and concisely.", }, { role = "user", content = function(context) local text = require("codecompanion.helpers.code").get_code(context.start_line, context.end_line) return "Please explain the following code:\n\n````" .. context.filetype .. "\n" .. text .. "\n````" end, }, }, }, }, }) ````` ::: A Markdown prompt has YAML frontmatter between `---` delimiters, then a `## system` or `## user` heading for each message. The frontmatter fields are: | Field | Required | Description | | --- | --- | --- | | `name` | Yes | The name shown in the Action Palette | | `interaction` | Yes | `chat`, `inline` or `workflow` | | `description` | No | The description shown in the Action Palette | | `opts` | No | See [Options](#options) | | `context` | No | See [Context](#context) | | `mcp_servers` | No | See [MCP Servers](#mcp-servers) | | `rules` | No | See [Rules](#rules) | | `skills` | No | See [Skills](#skills) | | `tools` | No | See [Tools](#tools) | The messages are optional. A prompt with only frontmatter opens an empty chat buffer with its context, rules, skills and tools loaded. In a Lua prompt, the table key is the name. The `${context.filetype}` and `${context.code}` above are [placeholders](#placeholders). ### Options A prompt that generates unit tests in a new buffer: ::: code-group ```markdown [Markdown] --- name: Add Docstrings interaction: inline description: Add docstrings to the selected code opts: alias: docstrings auto_submit: true modes: - v --- ## system Write docstrings in the style the rest of the file uses. ## user The code to generate tests for is #{buffer} ``` ```lua [Lua] require("codecompanion").setup({ prompt_library = { ["Generate Tests"] = { interaction = "inline", description = "Generate unit tests", opts = { alias = "tests", auto_submit = true, modes = { "v" }, placement = "new", stop_context_insertion = true, }, prompts = { { role = "system", content = "Generate comprehensive unit tests for the provided code.", }, { role = "user", content = "The code to generate tests for is #{buffer}", }, }, }, }, }) ``` ::: The available options are: | Option | Type | Description | | --- | --- | --- | | `adapter` | table | The adapter and model to use, as below | | `alias` | string | Run the prompt with `:CodeCompanion /{alias}` or `require("codecompanion").prompt("{alias}")` | | `approval_mode` | string | Start the chat buffer in an [approval mode](/usage/chat-buffer/agents-tools#approval-modes): `"ask"`, `"auto"` or `"yolo"` | | `auto_submit` | boolean | Send the prompt to the LLM straight away | | `callbacks` | table | [Callbacks](/configuration/callbacks) for the chat buffer the prompt opens (Lua only) | | `enabled` | boolean | Set to `false` to hide the prompt without removing it | | `ignore_system_prompt` | boolean | Don't send the chat [system prompt](/configuration/system-prompt). The tool system prompt is still sent | | `intro_message` | string | The intro message shown in the chat buffer, with each `\n` starting a new line | | `is_slash_cmd` | boolean | Make a chat prompt available as a slash command, using its `alias` | | `is_workflow` | boolean | Treat the prompt as a [workflow](#workflows) | | `modes` | table | Only show the prompt in these modes, such as `{ "v" }` for visual mode | | `placement` | string | For the inline interaction: `new`, `replace`, `add`, `before` or `chat` | | `pre_hook` | function | Run before the prompt, see [Pre-hooks](#pre-hooks) (Lua only) | | `stop_context_insertion` | boolean | Don't add the visual selection to the prompt automatically | | `user_prompt` | boolean | Ask for your input before the prompt runs | To use a different adapter and model: ::: code-group ```markdown [Markdown] --- name: Local Review interaction: chat description: Review code with a local model opts: adapter: name: ollama model: deepseek-coder:6.7b --- ## user Review #{buffer} for bugs and readability. ``` ```lua [Lua] require("codecompanion").setup({ prompt_library = { ["Local Review"] = { interaction = "chat", description = "Review code with a local model", prompts = { { role = "user", content = "Review #{buffer} for bugs and readability.", }, }, opts = { adapter = { name = "ollama", model = "deepseek-coder:6.7b", }, }, }, }, }) ``` ::: [ACP adapters](/configuration/adapters-acp) also take `acp_opts`, which sets [session config options](https://agentclientprotocol.com/protocol/session-config-options#session-config-options). Keys are the option's `category`, and values are the option's `value` or its `name`, case-insensitively: ::: code-group ```markdown [Markdown] --- name: Quick Review interaction: chat description: Fast review with low effort opts: adapter: name: claude_code model: Opus acp_opts: mode: plan thought_level: low --- ## user Review #{buffer} and list the three most important issues. ``` ```lua [Lua] require("codecompanion").setup({ prompt_library = { ["Quick Review"] = { interaction = "chat", description = "Fast review with low effort", prompts = { { role = "user", content = "Review #{buffer} and list the three most important issues.", }, }, opts = { adapter = { name = "claude_code", model = "Opus", acp_opts = { mode = "plan", thought_level = "low", }, }, }, }, }, }) ``` ::: > \[!TIP] > To see the options an agent supports, open a chat with that adapter and press `gd` for the debug window ### Placeholders Placeholders add dynamic content to a prompt with `${name}`. In Lua, `content` can also be a function that receives the buffer context. **Context** `${context.}` reads from the buffer the prompt was started from, along with the current date: ::: code-group ```markdown [Markdown] --- name: Buffer Info interaction: chat description: Show buffer information --- ## user I'm working in buffer ${context.bufnr} which is a ${context.filetype} file. ``` ```lua [Lua] require("codecompanion").setup({ prompt_library = { ["Buffer Info"] = { interaction = "chat", description = "Show buffer information", prompts = { { role = "user", content = function(context) return "I'm working in buffer " .. context.bufnr .. " which is a " .. context.filetype .. " file." end, }, }, }, }, }) ``` ::: The fields, with a visual selection of lines 8 to 10: ```lua { bufnr = 7, buftype = "", code = [[local function hello(text) return "hello " .. text end]], cursor_pos = { 10, 3 }, date = "Sunday, 11 October 2026", end_col = 3, end_line = 10, filename = "hello.lua", filetype = "lua", is_normal = false, is_visual = true, line_count = 42, lines = { "local function hello(text)", ' return "hello " .. text', "end" }, mode = "v", path = "/Users/Oli/Code/project/lua/hello.lua", relative_path = "lua/hello.lua", start_col = 1, start_line = 8, user_prompt = "", winnr = 1000, } ``` Without a selection, `code` is `false` and `lines` is empty. The `date` follows [`date_format`](/configuration/system-prompt#chat-system-prompt), which also sets the date in the system prompt. **External Lua Files** `${file.key}` loads `file.lua` from the prompt's directory and reads `key` from the table it returns: ``` .prompts/ ├── commit.md ├── commit.lua └── utils.lua ``` ::: code-group ````markdown [commit.md] --- name: Commit message interaction: chat description: Generate a commit message opts: alias: commit --- ## user You are an expert at following the Conventional Commit specification. Given the git diff listed below, please generate a commit message for me: ```diff ${commit.diff} ``` ```` ```lua [commit.lua] return { diff = function(args) return vim.system({ "git", "diff", "--no-ext-diff", "--staged" }, { text = true }):wait().stdout end, } ``` ::: A prompt can reference as many files as it likes, such as `${commit.diff}` and `${utils.git_log}` together. A value can be a string, or a function that receives an `args` table and returns one: ```lua return { summary = function(args) -- args.context is the buffer context and args.item is the whole prompt return "Working in " .. args.context.relative_path end, style = "Keep the summary to one line", } ``` ## Conditionals In Lua, a `condition` function controls whether a prompt appears in the Action Palette, or whether a single message is sent: ::: code-group ```lua [Prompt] require("codecompanion").setup({ prompt_library = { ["Visual Only"] = { interaction = "chat", description = "Only appears in visual mode", condition = function(context) return context.is_visual end, prompts = { { role = "user", content = "This prompt only appears when you're in visual mode.", }, }, }, }, }) ``` ```lua [Message] require("codecompanion").setup({ prompt_library = { ["Visual Only"] = { interaction = "chat", description = "Only sends the message in visual mode", prompts = { { role = "user", content = "This message is only sent when you're in visual mode.", condition = function(context) return context.is_visual end, }, }, }, }, }) ``` ::: ## Context To start a chat buffer with files, symbols or URLs already shared: ::: code-group ```markdown [Markdown] --- name: Test Context interaction: chat description: Add some context context: - type: file path: - lua/codecompanion/health.lua - lua/codecompanion/http.lua - type: symbols path: lua/codecompanion/interactions/chat/init.lua - type: url url: https://raw.githubusercontent.com/olimorris/codecompanion.nvim/refs/heads/main/lua/codecompanion/commands.lua --- ## user Explain how these files fit together. ``` ```lua [Lua] require("codecompanion").setup({ prompt_library = { ["Test Context"] = { interaction = "chat", description = "Add some context", context = { { type = "file", path = { "lua/codecompanion/health.lua", "lua/codecompanion/http.lua", }, }, { type = "symbols", path = "lua/codecompanion/interactions/chat/init.lua", }, { type = "url", url = "https://raw.githubusercontent.com/olimorris/codecompanion.nvim/refs/heads/main/lua/codecompanion/commands.lua", }, }, prompts = { { role = "user", content = "Explain how these files fit together.", opts = { contains_code = true, }, }, }, }, }, }) ``` ::: `path` and `url` take a single value or a list. URLs are fetched each time the prompt runs. A message with `contains_code = true` is left out when `opts.send_code` is `false`. ## MCP Servers To start [MCP servers](/configuration/mcp) with the prompt: ::: code-group ```markdown [Markdown] --- name: Prompt with MCP servers interaction: chat description: A prompt that starts MCP servers mcp_servers: - tavily-mcp - filesystem --- ## user Find the documentation for the library used in #{buffer} and summarise its API. ``` ```lua [Lua] require("codecompanion").setup({ prompt_library = { ["Prompt with MCP servers"] = { interaction = "chat", description = "A prompt that starts MCP servers", prompts = { { role = "user", content = "Find the documentation for the library used in #{buffer} and summarise its API.", }, }, mcp_servers = { "tavily-mcp", "filesystem", }, }, }, }) ``` ::: These replace the servers that have `add_to_chat = true`. Set `mcp_servers` to `none` to start no servers at all. ## Pickers In Lua, a picker opens a second menu of items built at runtime: ```lua require("codecompanion").setup({ prompt_library = { ["My picker menu ..."] = { interaction = " ", description = "My current items", picker = { prompt = "Select an item", columns = { "name", "description" }, items = { { name = "Item 1", description = "This is item 1", callback = function(context) print("You selected item 1") end, }, { name = "Item 2", description = "This is item 2", callback = function(context) print("You selected item 2") end, }, }, }, }, }, }) ``` `items` can also be a function that receives the buffer context and returns the list. ## Pre-hooks In Lua, `pre_hook` runs before the prompt. In the inline interaction with `placement = "new"`, it must return the number of the buffer to write the code into: ```lua require("codecompanion").setup({ prompt_library = { ["Boilerplate HTML"] = { interaction = "inline", description = "Generate some boilerplate HTML", opts = { placement = "new", pre_hook = function() local bufnr = vim.api.nvim_create_buf(true, false) vim.api.nvim_set_current_buf(bufnr) vim.api.nvim_set_option_value("filetype", "html", { buf = bufnr }) return bufnr end, }, prompts = { { role = "system", content = "You are an expert HTML programmer", }, { role = "user", content = "Please generate some HTML boilerplate for me. Return the code only and no markdown codeblocks", }, }, }, }, }) ``` ## Rules To load [rule groups](/configuration/rules#rule-groups) with the prompt: ::: code-group ```markdown [Markdown] --- name: Prompt with rules interaction: chat description: A prompt that loads rules rules: - default - my_other_rules --- ## user Explain how #{buffer} fits into the rest of the project. ``` ```lua [Lua] require("codecompanion").setup({ prompt_library = { ["Prompt with rules"] = { interaction = "chat", description = "A prompt that loads rules", prompts = { { role = "user", content = "Explain how #{buffer} fits into the rest of the project.", }, }, rules = { "default", "my_other_rules", }, }, }, }) ``` ::: Set `rules` to `none` to load no rules at all. > \[!NOTE] > A prompt that names no rules loads none by default. Enable `rules.opts.chat.autoload_groups_in_prompt_library` to load the groups in `rules.opts.chat.autoload` instead ## Skills To load [skills](/configuration/skills#prompt-library), or skill groups, with the prompt: ::: code-group ```markdown [Markdown] --- name: Review this PR interaction: chat description: Review the changes on this branch skills: - code-review --- ## user Review the changes in #{diff}. ``` ```lua [Lua] require("codecompanion").setup({ prompt_library = { ["Review this PR"] = { interaction = "chat", description = "Review the changes on this branch", prompts = { { role = "user", content = "Review the changes in #{diff}.", }, }, skills = { "code-review" }, }, }, }) ``` ::: These replace the skills in `autoload`. Set `skills` to `none` to load no skills at all. ## Tools To load [tools](/configuration/tools), or tool groups, with the prompt: ::: code-group ```markdown [Markdown] --- name: Prompt with tools interaction: chat description: A prompt that loads tools tools: - run_command - edit_file --- ## user Run the test suite and fix any failures. ``` ```lua [Lua] require("codecompanion").setup({ prompt_library = { ["Prompt with tools"] = { interaction = "chat", description = "A prompt that loads tools", prompts = { { role = "user", content = "Run the test suite and fix any failures.", }, }, tools = { "run_command", "edit_file", }, }, }, }) ``` ::: These are added alongside the [default tools](/configuration/tools#default-tools). Set `tools` to `none` to load no tools at all, including the defaults. ## Workflows A *workflow* chains prompts together. The first prompt is sent to the LLM, and once it responds, the next is added to the chat buffer, and so on. Use one for multi-step work such as writing code, then its tests: ::: code-group ```markdown [Markdown] --- name: Library workflow interaction: chat description: Build and test a library class opts: adapter: name: copilot model: gpt-4.1 is_workflow: true --- ## user Generate a Python class for managing a book library with methods for adding, removing and searching books ## user Write unit tests for the library class you just created ## user Add type hints and docstrings to the class and its tests ``` ```lua [Lua] require("codecompanion").setup({ prompt_library = { ["Library workflow"] = { interaction = "chat", description = "Build and test a library class", opts = { adapter = { name = "copilot", model = "gpt-4.1", }, is_workflow = true, }, prompts = { { { role = "user", content = "Generate a Python class for managing a book library with methods for adding, removing and searching books", }, }, { { role = "user", content = "Write unit tests for the library class you just created", }, }, { { role = "user", content = "Add type hints and docstrings to the class and its tests", }, }, }, }, }, }) ``` ::: In Lua, each inner table is one step. In Markdown, each `## user` heading is a step, and any `## system` messages are sent with the first. To change the options for a single step, such as submitting it automatically or switching model, give it its own `opts`. In Markdown, add a `yaml opts` code block under the heading: ::: code-group ````markdown [Markdown] ## user Generate a Python class for managing a book library with methods for adding, removing and searching books ## user ```yaml opts auto_submit: true ``` Write unit tests for the library class you just created ## user ```yaml opts adapter: name: copilot model: claude-haiku-4.5 auto_submit: false ``` Add type hints and docstrings to the class and its tests ```` ```lua [Lua] prompts = { { { role = "user", content = "Generate a Python class for managing a book library with methods for adding, removing and searching books", }, }, { { role = "user", content = "Write unit tests for the library class you just created", opts = { auto_submit = true, }, }, }, { { role = "user", content = "Add type hints and docstrings to the class and its tests", opts = { adapter = { name = "copilot", model = "claude-haiku-4.5", }, auto_submit = false, }, }, }, }, ``` ::: > \[!NOTE] > Markdown prompts don't support [agentic workflows](/extending/agentic-workflows), which need Lua ## Hiding Built-in Prompts To hide the prompts that ship with CodeCompanion from the Action Palette: ```lua require("codecompanion").setup({ display = { action_palette = { opts = { show_preset_prompts = false, }, }, }, }) ``` ## Limitations Workflows ignore the `ignore_system_prompt`, `intro_message`, `pre_hook`, `stop_context_insertion` and `user_prompt` options. --- --- url: /configuration/rules.md description: >- Create rule groups from files such as AGENTS.md, CLAUDE.md and Cursor rules, choose which load with every chat and set how they're parsed. --- # Configuring Rules LLMs don't remember anything between chats, so your instructions and project context have to be shared again each time. *Rules* are the files that hold them, such as `AGENTS.md` or `CLAUDE.md`, and CodeCompanion collects them into *rule groups* that are added to the chat buffer. To use them in a chat and write your own, see [Using Rules](/usage/chat-buffer/rules). ## Enabling Rules Rules are enabled by default, and the `default` group is added to every new chat buffer. To turn them off, or only add them to some chats: ::: code-group ```lua [Disable] require("codecompanion").setup({ rules = { opts = { chat = { enabled = false, }, }, }, }) ``` ```lua [Conditional] require("codecompanion").setup({ rules = { opts = { chat = { ---@param chat CodeCompanion.Chat ---@return boolean enabled = function(chat) return chat.adapter.type == "http" end, }, }, }, }) ``` ::: ### Default Group The `default` group looks for these files, and skips any that don't exist: | File | Used By | Parser | | --- | --- | --- | | `.clinerules` | Cline | | | `.cursorrules` | Cursor | | | `.goosehints` | Goose | | | `.rules` | Zed | | | `.windsurfrules` | Windsurf | | | `.github/copilot-instructions.md` | Copilot | | | `AGENT.md` | Agents | `claude` | | `AGENTS.md` | Agents | `claude` | | `CLAUDE.md` | Claude Code | `claude` | | `CLAUDE.local.md` | Claude Code | `claude` | | `~/.claude/CLAUDE.md` | Claude Code | `claude` | Relative paths are looked up from the current working directory. To replace the group, define your own `default` in `rules`. ## Rule Groups A *rule group* is a named list of files and directories: ```lua require("codecompanion").setup({ rules = { my_project_rules = { description = "Rule files for My Project", files = { "CLAUDE.md", "~/.claude/CLAUDE.md", "docs/**/*.md", { path = "CLAUDE.local.md", parser = "claude" }, { path = "~/.config/rules", files = "*.md" }, }, }, }, }) ``` Each entry in `files` can be: | Entry | Example | Adds | | --- | --- | --- | | A path | `"CLAUDE.md"` | The file, or every file in it if it's a directory | | A glob | `"docs/**/*.md"` | Every matching file | | A path with a parser | `{ path = "CLAUDE.local.md", parser = "claude" }` | The file, read with that [parser](#parsers) | | A directory with patterns | `{ path = ".", files = { ".clinerules", "*.md" } }` | Files in the directory matching a pattern | Paths can be absolute or relative to the current working directory. A directory with patterns can take a `parser` too, which applies to every file it matches. ### Conditional Groups `enabled` hides a group from the [/rules](/usage/chat-buffer/slash-commands#rules) slash command and the *Chat with rules ...* action when it returns `false`: ```lua require("codecompanion").setup({ rules = { my_project_rules = { description = "Rule files for My Project", ---@return boolean enabled = function() return vim.fn.getcwd():find("my_project", 1, true) ~= nil end, files = { "CLAUDE.md", "CLAUDE.local.md", }, }, }, }) ``` ### Nested Groups A group's `files` can hold other groups instead of a list, so one `enabled` condition and `parser` apply to all of them: ```lua require("codecompanion").setup({ rules = { my_project_rules = { description = "Rule files for My Project", parser = "claude", files = { ["mcp"] = { description = "The MCP implementation in My Project", files = { ".rules/mcp/mcp.md", }, }, ["tests"] = { description = "How tests are written in My Project", files = { ".rules/tests.md", }, }, }, }, }, }) ``` The slash command and the action list each nested group separately, as `my_project_rules/mcp` and `my_project_rules/tests`. CodeCompanion's own `CodeCompanion` group does this, with a group for each part of the plugin, and only appears when the current working directory has a `.codecompanion` directory. To hide the preset `default` and `CodeCompanion` groups from the list: ```lua require("codecompanion").setup({ rules = { opts = { show_presets = false, }, }, }) ``` ## Autoload Groups in `autoload` are added to every new chat buffer. It defaults to `"default"`: ::: code-group ```lua [Single Group] require("codecompanion").setup({ rules = { opts = { chat = { autoload = "my_project_rules", }, }, }, }) ``` ```lua [Multiple Groups] require("codecompanion").setup({ rules = { opts = { chat = { autoload = { "default", "my_project_rules" }, }, }, }, }) ``` ```lua [Conditional] require("codecompanion").setup({ rules = { opts = { chat = { ---@return string|string[] autoload = function() if vim.fn.getcwd():find("another_project", 1, true) ~= nil then return { "my_project", "another_project" } end return "my_project" end, }, }, }, }) ``` ::: ### Prompt Library A [prompt library](/configuration/prompt-library) item only gets rules if it names them in its own `rules` field. To give the items that name none your `autoload` groups: ```lua require("codecompanion").setup({ rules = { opts = { chat = { autoload_groups_in_prompt_library = true, }, }, }, }) ``` An item that names its own rules still uses those instead, and `rules = "none"` loads none. ## Parsers A *parser* changes how a rules file is shared with the LLM. Without one, the file is shared as it is. The built-in parsers are: | Parser | Description | | --- | --- | | `claude` | Share the whole file, plus any file it references on an `@` line, like Claude Code [does](https://code.claude.com/docs/en/memory#claude-md-imports) | | `codecompanion` | Share the content under `##` headings and any `@` files, and use a `## System Prompt` section as the system prompt | | `cli` | Share only the paths of `@` files. Used by the `/rules` slash command in the [CLI interaction](/usage/cli) | | `none` | Share the file unchanged | The `claude` and `codecompanion` parsers need markdown files. How `@` paths are resolved is covered in [Resolving `@` Paths](/usage/chat-buffer/rules#resolving-paths). To write your own, see [Creating Rules Parsers](/extending/parsers). ### Applying Parsers A parser can be set on a group, to apply to every file in it, or on a single file. A file's parser takes precedence over its group's: ::: code-group ```lua [Group Level] require("codecompanion").setup({ rules = { claude = { description = "Rules for Claude Code users", parser = "claude", files = { "CLAUDE.md", "CLAUDE.local.md", "~/.claude/CLAUDE.md", }, }, }, }) ``` ```lua [File Level] require("codecompanion").setup({ rules = { claude = { description = "Rules for Claude Code users", files = { { path = "CLAUDE.md", parser = "claude" }, { path = "CLAUDE.local.md", parser = "claude" }, { path = "~/.claude/CLAUDE.md", parser = "claude" }, }, }, }, }) ``` ```lua [Disable] require("codecompanion").setup({ rules = { claude = { description = "Rules for Claude Code users", parser = "none", files = { "CLAUDE.md", "CLAUDE.local.md", "~/.claude/CLAUDE.md", }, }, }, }) ``` ::: ## Syncing Referenced Files A file referenced on an `@` line that's open in a buffer is [synced](/usage/chat-buffer/#context) to the chat buffer, sending only what's changed on each turn. To send its entire content instead: ```lua require("codecompanion").setup({ rules = { opts = { chat = { default_params = "all", -- Can be "all" or "diff" }, }, }, }) ``` --- --- url: /configuration/skills.md description: >- Choose where CodeCompanion finds SKILL.md files, group skills together and load them into every chat. --- # Configuring Skills *Skills* are folders of instructions that an LLM reads when it needs them ([source](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview)). CodeCompanion only gives the LLM each skill's name and description up front, and the LLM reads the full skill when it applies. This is *progressive disclosure*, and it's why a chat can hold many skills without filling its context window. CodeCompanion uses the same `SKILL.md` [format as Claude](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview#how-skills-work). To use skills in a chat, see [Using Skills](/usage/chat-buffer/skills). ## Enabling and Disabling Skills Skills are enabled by default. To disable them: ```lua require("codecompanion").setup({ skills = { opts = { chat = { enabled = false, }, }, }, }) ``` ## Directories Skills are loaded from `skills.dirs`, which defaults to: | Directory | Description | | --- | --- | | `~/.config/codecompanion/skills` | Your personal skills | | `.codecompanion/skills` | The project's skills | | `~/.agents/skills` | Personal skills shared with other agents | | `~/.claude/skills` | Claude Code's personal skills | | `.claude/skills` | Claude Code's project skills | To change them: ```lua require("codecompanion").setup({ skills = { dirs = { "~/.config/codecompanion/skills", ".codecompanion/skills", "~/Code/team-skills", }, }, }) ``` Skills are keyed on the `name` in their YAML frontmatter. A directory further down the list takes precedence if skills of the same name clash, which also means a directory symlinked to another doesn't duplicate its skills. A `SKILL.md` is found up to five directories deep, so you can clone a repository of skills straight into a skills directory. Hidden directories such as `.git` are skipped. To change the depth: ```lua require("codecompanion").setup({ skills = { opts = { depth = 3, }, }, }) ``` Skills aren't cached, so new ones are picked up without restarting Neovim. ## Groups A *group* adds several skills under one name. Groups sit alongside `dirs` in the `skills` table: ```lua require("codecompanion").setup({ skills = { ["Senior Engineer"] = { description = "How I want code written and reviewed", skills = { "lua-developer", "code-review", "tdd" }, }, ["Technical Writer"] = { description = "How I want the docs written", skills = { "docs-voice", "changelog" }, }, }, }) ``` A group lists skills by the `name` in their frontmatter, not by path, so each skill must be in one of your [directories](#directories). ## Autoload To add skills and groups to every new chat buffer: ::: code-group ```lua [Static] require("codecompanion").setup({ skills = { opts = { chat = { autoload = { "Senior Engineer", "lua-developer" }, }, }, }, }) ``` ```lua [Conditional] require("codecompanion").setup({ skills = { opts = { chat = { ---@return string[] autoload = function() if vim.fn.getcwd():find("my_project", 1, true) then return { "Senior Engineer" } end return {} end, }, }, }, }) ``` ::: The inline interaction has its own `autoload`, covered in [Configuring the Inline Interaction](/configuration/inline#rules-and-skills). ## Creating Skills A skill is a directory containing a `SKILL.md`, in one of your [directories](#directories): ``` lua-developer/ ├── SKILL.md # Required ├── REFERENCE.md # Optional └── scripts/ # Optional └── lint.sh ``` `SKILL.md` is markdown with YAML frontmatter, as described on Claude's [Agent Skills](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview#how-skills-work) page. Both `name` and `description` are required: ```markdown --- name: lua-developer description: The Lua conventions for this project. Use when writing or reviewing Lua. --- # Lua Developer Follow these conventions when writing Lua in this project: - Two space indent, 120 columns - `snake_case` for functions, `PascalCase` for classes See [REFERENCE.md](REFERENCE.md) for worked examples. Run [scripts/lint.sh](scripts/lint.sh) before you report the work as finished. ``` The LLM only sees the description until it reads the skill, so a vague description means the skill may never be used. Link to other files in the skill with paths relative to `SKILL.md`. The LLM is given the full path to `SKILL.md`, so it finds them wherever you started Neovim from. ## Pickers The [/skills](/usage/chat-buffer/slash-commands#skills) and [/skills-group](/usage/chat-buffer/slash-commands#skills-group) slash commands use Telescope, fzf-lua, mini.pick or Snacks, whichever is installed first in that order, and fall back to `vim.ui.select`. To choose one: ```lua require("codecompanion").setup({ interactions = { chat = { slash_commands = { ["skills"] = { opts = { provider = "snacks", -- Can be "default", "telescope", "fzf_lua", "mini_pick" or "snacks" }, }, ["skills-group"] = { opts = { provider = "snacks", }, }, }, }, }, }) ``` ## Prompt Library A [prompt library](/configuration/prompt-library) item can name the skills it needs, and they're added to the chat buffer when the prompt runs. These replace `autoload` for that chat: ::: code-group ```markdown [Markdown] --- name: Review this PR interaction: chat description: Review the changes on this branch skills: - code-review --- ## user Review the changes in #{diff}. ``` ```lua [Lua] require("codecompanion").setup({ prompt_library = { ["Review this PR"] = { interaction = "chat", description = "Review the changes on this branch", skills = { "code-review" }, prompts = { { role = "user", content = "Review the changes in #{diff}.", }, }, }, }, }) ``` ::: To start a prompt with no skills at all, ignoring `autoload`: ::: code-group ```markdown [Markdown] --- name: Explain this code interaction: chat description: Explain the selected code skills: none --- ## user Explain how the selected code works. ``` ```lua [Lua] require("codecompanion").setup({ prompt_library = { ["Explain this code"] = { interaction = "chat", description = "Explain the selected code", skills = "none", prompts = { { role = "user", content = "Explain how the selected code works.", }, }, }, }, }) ``` ::: ## Limitations * Skills only work with HTTP adapters that support tools, so they're unavailable with ACP adapters * The LLM reads a skill with the `read_file` tool, so a skill isn't added if that tool is disabled. `run_command` is added too, when it's enabled, so the LLM can run a skill's scripts * Reading the frontmatter needs the Tree-sitter YAML parser. Install it with `:TSInstall yaml` --- --- url: /configuration/system-prompt.md description: >- Replace or extend the system prompts CodeCompanion sends with every chat and tool request. --- # Configuring System Prompts A *system prompt* sets how the LLM behaves before it sees your first message. CodeCompanion sends one with every request from a chat buffer, and a second one when [tools](/usage/chat-buffer/agents-tools) are in use. > \[!NOTE] > System prompts only apply to HTTP adapters. ACP agents bring their own, which you can [add to or replace](/configuration/adapters-acp#setting-a-system-prompt) ## Chat System Prompt The default system prompt keeps responses short and focused on development and Neovim: `````txt You are an AI programming assistant named "CodeCompanion", working within the Neovim text editor. Follow the user's requirements carefully and to the letter. Use the context and attachments the user provides. Keep your answers short and impersonal. Use Markdown formatting in your answers. DO NOT use H1 or H2 headers. When suggesting code changes, use Markdown code blocks with four backticks. Add the language ID and file path (in curly braces) after the opening backticks. Omit the file path if you want the user to decide where to place the code. Use a line comment with '...existing code...' to indicate unchanged code, using the correct comment syntax for the language. Example: ````languageId {path/to/file} // ...existing code... { changed code } // ...existing code... ```` DO NOT include diff formatting or line numbers unless asked. DO NOT wrap the whole response in triple backticks. When given a task: 1. Think step-by-step. For complex architectural changes, describe your plan first. 2. Only include relevant code in code blocks — avoid repeating unchanged code. 3. End with a short suggestion for the next user turn. Additional context: All non-code text responses must be written in the ${language} language. The user's current working directory is ${cwd}. The current date is ${date}. The user's Neovim version is ${nvim_version}. The user is working on a ${os} machine. Please respond with system specific commands if applicable. ````` The language comes from `opts.language`, which defaults to `English`. To change the date format: ```lua require("codecompanion").setup({ interactions = { opts = { date_format = "%A, %d %B %Y", -- Example: "Monday, 01 January 2024" }, }, }) ``` Press `gs` in a chat buffer to toggle the system prompt on and off. ## Tool System Prompt When tools are in use, CodeCompanion adds a second system prompt after the first: `````txt The user will ask a question, or ask you to perform a task, and it may require research to answer correctly. There is a selection of tools that let you perform actions or retrieve helpful context to answer the user's question. You will be given some context and attachments along with the user prompt. You can use them if they are relevant to the task, and ignore them if not. If you aren't sure which tool is relevant, you can call multiple tools. You can call tools repeatedly to take actions or gather as much context as needed until you have completed the task fully. Don't give up unless you are sure the request cannot be fulfilled with the tools you have. Don't make assumptions about the situation - gather context first, then perform the task or answer the question. Don't repeat yourself after a tool call, pick up where you left off. You don't need to read a file if it's already provided in context. Only when the user asks you to write, change or debug code: - If you can infer the project type (languages, frameworks, and libraries) from the user's query or the context that you have, keep them in mind when making changes. - If the user wants you to implement a feature and they have not specified the files to edit, first break down the user's request into smaller concepts and think about the kinds of files you need to grasp each concept. - Think creatively and explore the workspace in order to make a complete fix. - NEVER print out a codeblock with a terminal command to run unless the user asked for it. When using a tool, follow the json schema very carefully and make sure to include ALL required properties. Always output valid JSON when using a tool. If a tool exists to do a task, use the tool instead of asking the user to manually take an action. If you say that you will take an action, then go ahead and use the tool to do it. No need to ask permission. Never use a tool that does not exist. Use tools using the proper procedure, DO NOT write out a json codeblock with the tool inputs. Never say the name of a tool to a user. For example, instead of saying that you'll use the edit_file tool, say "I'll edit the file". For maximum efficiency, whenever you need to perform multiple independent operations, invoke all relevant tools simultaneously rather than sequentially. When invoking a tool that takes a file path, always use the file path you have been given by the user or by the output of a tool. Use proper Markdown formatting in your answers. When referring to a filename or symbol in the user's workspace, wrap it in backticks. Any code block examples must be wrapped in four backticks with the programming language. ````languageId // Your code here ```` The languageId must be the correct identifier for the programming language, e.g. python, javascript, lua, etc. If you are providing code changes, use the edit_file tool (if available to you) to make the changes directly instead of printing out a code block with the changes. ````` ## Changing System Prompts ### Chat To replace the chat system prompt: ```lua require("codecompanion").setup({ interactions = { chat = { opts = { system_prompt = "You are a senior Lua developer. Answer in British English.", }, }, }, }) ``` `system_prompt` can also be a function that receives a context table and returns a string. To extend the default rather than replace it: ```lua require("codecompanion").setup({ interactions = { chat = { opts = { ---@param ctx CodeCompanion.SystemPrompt.Context ---@return string system_prompt = function(ctx) return ctx.default_system_prompt .. string.format( [[Additional context: All non-code text responses must be written in the %s language. The current date is %s. The user's Neovim version is %s. The user is working on a %s machine. Please respond with system specific commands if applicable. ]], ctx.language, ctx.date, ctx.nvim_version, ctx.os ) end, }, }, }, }) ``` The context table contains: | Field | Description | | --- | --- | | `adapter` | The chat's adapter | | `cwd` | The current working directory | | `date` | The date, formatted with `date_format` | | `default_system_prompt` | The default system prompt, without the additional context | | `language` | The value of `opts.language` | | `nvim_version` | The Neovim version, such as `0.11.2` | | `os` | The operating system, such as `Mac`, `Linux` or `Windows` | | `project_root` | The closest parent directory containing `.git` or `.svn`, if there is one | ### Tools The tool system prompt is configured under `interactions.chat.tools.opts.system_prompt`: ```lua require("codecompanion").setup({ interactions = { chat = { tools = { opts = { system_prompt = { enabled = true, replace_main_system_prompt = false, ---@param args { ctx: CodeCompanion.SystemPrompt.Context, tools: string[] } ---@return string prompt = function(args) return "Use the tools you have to finish the task before replying." end, }, }, }, }, }, }) ``` Set `enabled = false` to stop sending it, or `replace_main_system_prompt = true` to send it in place of the chat system prompt. `prompt` can be a string, or a function that receives the context table and the names of the tools in use. ## When System Prompts Change CodeCompanion rebuilds the system prompt when you: * Change adapter * Change model on an HTTP adapter * Clear the chat * Add a tool, which updates the tool system prompt Adding [rules](/configuration/rules) doesn't change the system prompt. They're shared as context, along with any system message their parser adds. ## Limitations * The inline interaction uses its own system prompt, which can't be changed --- --- url: /configuration/others.md description: >- Set the language LLMs respond in, the log level, per-project config and whether code can be sent to an LLM. --- # Configuring Other Options ## Language The default system prompt asks the LLM to respond in English. To change the language: ```lua require("codecompanion").setup({ opts = { language = "French", }, }) ``` If you've written your own system prompt, set the language in it instead. ## Log Level Logs are written to `codecompanion.log` in `stdpath("log")`, which is `~/.local/state/nvim` on most systems. To log more detail when debugging: ```lua require("codecompanion").setup({ opts = { log_level = "DEBUG", -- Can be "TRACE", "DEBUG", "INFO", "WARN" or "ERROR" }, }) ``` The default is `ERROR`. ## Per-Project Configuration When you work across several projects, each can have its own CodeCompanion config. List files to look for in the current working directory, or key a config by directory: ::: code-group ```lua [Files] require("codecompanion").setup({ opts = { per_project_config = { files = { ".codecompanion", ".codecompanion.lua", }, }, }, }) ``` ```lua [Dirs] require("codecompanion").setup({ opts = { per_project_config = { paths = { ["~/Code/Python/New-Startup"] = { interactions = { chat = { adapter = { name = "copilot", model = "claude-opus-4.6", }, }, }, }, }, }, }, }) ``` ::: A directory only matches when it's the working directory itself, not a parent of it. A matching config is merged over your own, and a file takes precedence over a directory if both match. A file must return a Lua table: ```lua return { interactions = { chat = { adapter = { name = "copilot", model = "claude-sonnet-4.6", }, tools = { opts = { default_tools = { "memory", }, }, }, }, }, } ``` To turn per-project config off, set `opts.per_project_config.enabled = false`. > \[!NOTE] > Per-project config is read once, when `setup()` runs, so changing directory afterwards doesn't load another project's config ## Sending Code > \[!WARNING] > CodeCompanion makes every attempt to stop code reaching the LLM, but use this option at your own risk To stop editor context, slash commands and prompts marked as containing code from being sent to the LLM: ```lua require("codecompanion").setup({ opts = { send_code = false, }, }) ``` `send_code` can also be a function that returns a boolean, so you can decide per request. --- --- url: /extending/adapters.md description: Build your own HTTP adapter to connect CodeCompanion to any LLM. --- # Extending with Adapters An *adapter* is the bridge between CodeCompanion and an LLM's API. It describes the endpoint, the headers and parameters to send, and how to turn CodeCompanion's messages into a request and the LLM's response back into text, reasoning and tool calls. The built-in adapters live in the [adapters directory](https://github.com/olimorris/codecompanion.nvim/tree/main/lua/codecompanion/adapters/http). > \[!TIP] > If your LLM is "OpenAI compatible", extend the `openai_legacy` adapter or use `openai_compatible` instead of writing one from scratch. The [xAI](https://github.com/olimorris/codecompanion.nvim/blob/main/lua/codecompanion/adapters/http/xai.lua) adapter does this ## The Interface An HTTP adapter is a table with the following fields (abridged from `lua/codecompanion/adapters/http/init.lua`): ```lua ---@class CodeCompanion.HTTPAdapter ---@field name string The name of the adapter e.g. "openai" ---@field formatted_name string The formatted name of the adapter e.g. "OpenAI" ---@field roles table The mapping of roles in the config to the LLM's defined roles ---@field features table The features that the adapter supports ---@field url string The URL of the LLM to connect to ---@field env? table Environment variables which can be referenced in the parameters ---@field env_replaced? table Replacement of environment variables with their actual values ---@field headers table The headers to pass to the request ---@field parameters table The parameters to pass to the request ---@field body table Additional body parameters to pass to the request ---@field raw? table Any additional curl arguments to pass to the request ---@field opts? table Additional options for the adapter ---@field handlers CodeCompanion.HTTPAdapter.Handlers Functions which link the output from the request to CodeCompanion ---@field schema table Set of parameters for the LLM that the user can customise in the chat buffer ``` The fields up to `handlers` describe the API and are passed to curl. The `handlers` do the real work, translating between CodeCompanion and the LLM. `roles` maps CodeCompanion's roles to the LLM's. Messages reach your handlers with their roles already mapped: ```lua roles = { llm = "assistant", user = "user", tool = "tool", }, ``` `opts` holds flags such as `stream`, `tools`, `vision` and `documents`. `features.tokens = true` is required for the `parse_tokens` handler to be called. ## Environment Variables APIs need values injected into different parts of the request. Azure OpenAI puts the endpoint, deployment and API version in the URL, whilst OpenAI takes the API key in an `Authorization` header. Declare them in `env` and reference them with `${}`: ```lua url = "${endpoint}/openai/deployments/${deployment}/chat/completions?api-version=${api_version}", env = { api_key = "AZURE_OPENAI_API_KEY", endpoint = "AZURE_OPENAI_ENDPOINT", api_version = "2024-06-01", deployment = "schema.model.default", }, headers = { ["Content-Type"] = "application/json", ["api-key"] = "${api_key}", }, ``` Variables are replaced in the `url`, `headers`, `parameters` and `raw` fields on every request. Each value is resolved in this order: | Value | Example | Resolves to | | --- | --- | --- | | `cmd:` prefix | `"cmd:op read op://personal/Gemini_API/credential --no-newline"` | The command's output, with trailing whitespace removed | | `file:` prefix | `"file:~/.secrets/gemini"` | The file's contents, with trailing whitespace removed | | Environment variable | `"GEMINI_API_KEY"` | The variable's value, if it's set | | Function | `function(self) return os.getenv("GEMINI_API_KEY") end` | The function's return value, called with the adapter | | Schema path | `"schema.model.default"` | The value at that path on the adapter | | Anything else | `"2024-06-01"` | The string, unchanged | Commands time out after 20 seconds. This can be changed with: ```lua require("codecompanion").setup({ adapters = { opts = { cmd_timeout = 20000, -- milliseconds }, }, }) ``` > \[!WARNING] > A schema path returns the raw value. If `schema.model.default` is a function, use a function in `env` instead ## Handlers Handlers are grouped into four tables. Each one takes `self` and a single `args` table, so new fields can be added without breaking your adapter: ```lua handlers = { lifecycle = { setup = function(self) end, on_exit = function(self, args) end, teardown = function(self) end, }, request = { build_parameters = function(self, args) end, build_messages = function(self, args) end, build_tools = function(self, args) end, build_structured_output = function(self, args) end, build_reasoning = function(self, args) end, build_body = function(self, args) end, }, response = { parse_chat = function(self, args) end, parse_inline = function(self, args) end, parse_tokens = function(self, args) end, parse_meta = function(self, args) end, }, tools = { format_calls = function(self, args) end, format_response = function(self, args) end, }, } ``` All of them are optional. Every built-in adapter has its own tests, which show how each one handles the API's output. ### Lifecycle | Handler | `args` | Description | | --- | --- | --- | | `setup` | | Run before the request is sent and before environment variables are resolved. Return `false` to cancel the request | | `on_exit` | `data` | Run when the request completes, or with no `data` when the user stops it | | `teardown` | | Run last, after `on_exit` | ### Request Except for `build_reasoning`, their return values are merged into the request body: | Handler | `args` | Description | | --- | --- | --- | | `build_parameters` | `params`, `messages` | Set the request's parameters | | `build_messages` | `messages` | Format the messages for the LLM | | `build_tools` | `tools` | Convert CodeCompanion's tool schemas into the LLM's format | | `build_structured_output` | `schema` | Convert a structured output schema into the LLM's format | | `build_reasoning` | `data` | Combine the streamed reasoning chunks into one, to store on the LLM's message | | `build_body` | `payload` | Add anything else to the body | ### Response | Handler | `args` | Description | | --- | --- | --- | | `parse_chat` | `data`, `tools` | Parse a response for the chat buffer, writing any tool calls into `tools` | | `parse_inline` | `data`, `context` | Parse a response for the inline interaction | | `parse_tokens` | `data` | Return the token count from a response | | `parse_meta` | `data` | Process non-standard fields that `parse_chat` returns in `extra` | ### Tools | Handler | `args` | Description | | --- | --- | --- | | `format_calls` | `tools` | Format the LLM's tool calls for the next request | | `format_response` | `tool_call`, `output` | Format a tool's output as a message for the LLM | ## Building the Handlers The examples below build the main handlers for OpenAI's Chat Completions API, as in the `openai_legacy` adapter. Put this at the top of your adapter: ```lua local adapter_utils = require("codecompanion.adapters.utils") local log = require("codecompanion.utils.log") ``` ### `build_messages` OpenAI expects a `messages` array of `role` and `content`: ```sh curl https://api.openai.com/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -d '{ "model": "gpt-4.1", "messages": [ { "role": "user", "content": "Explain Ruby in two words" } ] }' ``` CodeCompanion passes the chat buffer's messages in `args.messages`, each with at least a `role` and `content`: ```lua { { role = "user", content = "Explain Ruby in two words", }, } ``` That's already the right shape, so the handler returns them as they are: ```lua handlers = { request = { build_messages = function(self, args) return { messages = args.messages } end, }, } ``` ### `build_parameters` OpenAI needs no extra parameters, so this returns them unchanged: ```lua handlers = { request = { build_parameters = function(self, args) return args.params end, }, } ``` ### `parse_chat` A streamed response arrives in chunks, and CodeCompanion calls `parse_chat` with each one in `args.data`: ```txt data: {"id":"chatcmpl-90DdmqMKOKpqFemxX0OhTVdH042gu","object":"chat.completion.chunk","created":1709839462,"model":"gpt-4.1","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]} data: {"id":"chatcmpl-90DdmqMKOKpqFemxX0OhTVdH042gu","object":"chat.completion.chunk","created":1709839462,"model":"gpt-4.1","choices":[{"index":0,"delta":{"content":"Programming"},"finish_reason":null}]} data: [DONE] ``` Each chunk is prefixed with `data: `, so it isn't valid JSON. `adapter_utils.clean_streamed_data` strips the prefix, and `luanil = { object = true }` keeps `null` values as `nil`: ```lua handlers = { response = { parse_chat = function(self, args) if not args.data or args.data == "" then return nil end local data = adapter_utils.clean_streamed_data(args.data) local ok, json = pcall(vim.json.decode, data, { luanil = { object = true } }) if not ok or not json.choices or #json.choices == 0 then return nil end local delta = json.choices[1].delta return { status = "success", output = { role = delta.role, content = delta.content, }, } end, }, } ``` **`parse_chat` must return a table with `status` and `output`**. Return `nil` for anything it can't parse, such as `[DONE]` or an error, and leave errors to `on_exit`. To show reasoning, add `output.reasoning = { content = "..." }`. ### `parse_meta` Some OpenAI-compatible providers, such as DeepSeek and OpenRouter, add non-standard fields to the [`message`](https://platform.openai.com/docs/api-reference/chat/object#chat-object-choices-message) or [`delta`](https://platform.openai.com/docs/api-reference/chat-streaming/streaming#chat_streaming-streaming-choices-delta) object. The OpenAI-based adapters return these in an `extra` table from `parse_chat`, and `parse_meta` is called whenever `extra` is present. DeepSeek streams its reasoning in `delta.reasoning_content`. To move it into the reasoning output: ```lua handlers = { response = { parse_meta = function(self, args) local data = args.data if data.extra.reasoning_content then data.output.reasoning = { content = data.extra.reasoning_content } -- An empty string is treated as a normal response if data.output.content == "" then data.output.content = nil end end return data end, }, } ``` Always return `data`. Setting `output.content` to `nil` is only needed when streaming and can cause problems without it. ### `parse_inline` The inline interaction doesn't stream, so `args.data` is the whole response, with the JSON in `args.data.body`. Return the text to write into the buffer: ```lua handlers = { response = { parse_inline = function(self, args) if not args.data or args.data == "" then return nil end local ok, json = pcall(vim.json.decode, args.data.body, { luanil = { object = true } }) if not ok then return { status = "error", output = json } end return { status = "success", output = json.choices[1].message.content } end, }, } ``` `args.context` holds details of the buffer that started the request. ### `on_exit` Errors from a streaming endpoint are hard to parse, as they arrive across several `data:` lines. `on_exit` receives the final response instead: ```lua { body = '{\n "error": {\n "message": "Incorrect API key provided: sk-F18b****XdwS.",\n "type": "invalid_request_error",\n "param": null,\n "code": "invalid_api_key"\n }\n}', exit = 0, headers = { "date: Thu, 03 Oct 2024 08:05:32 GMT" }, status = 401, } ``` That's easier to work with: ```lua handlers = { lifecycle = { on_exit = function(self, args) if args.data and args.data.status >= 400 then log:error("Error: %s", args.data.body) end end, }, } ``` `log:error` writes to the log file and notifies the user. ### `setup` and `teardown` `setup` runs before environment variables are resolved, which is why the Copilot adapter uses it to fetch a token. It runs on a copy of the adapter made for each request, so changes don't carry over to the next one: ```lua handlers = { lifecycle = { setup = function(self) if self.opts.stream then self.parameters.stream = true end return true end, teardown = function(self) end, }, } ``` ### Utilities Many "OpenAI compatible" endpoints have quirks: * System messages must come first (`anthropic`, `deepseek`) * System messages must be a single message (`anthropic`, `deepseek`) * Messages must alternate between user and LLM (`deepseek`) The [adapter utilities](https://github.com/olimorris/codecompanion.nvim/blob/main/lua/codecompanion/adapters/utils/init.lua), such as `merge_system_messages` and `merge_messages`, handle these. The built-in adapters are the best reference for how to use them. ## Tools To support [function calling](https://platform.openai.com/docs/guides/function-calling?api-mode=chat), set `opts.tools = true` and have `parse_chat` write tool calls into `args.tools`. Then add: * **`build_tools`** - converts CodeCompanion's tool schemas into the LLM's format * **`format_calls`** - [formats](https://platform.openai.com/docs/guides/function-calling?api-mode=chat#handling-function-calls) the LLM's tool calls for the next request * **`format_response`** - formats a tool's output as a message for the chat buffer's message history ```lua handlers = { request = { build_tools = function(self, args) if not self.opts.tools or not args.tools then return nil end local transformed = {} for _, tool in pairs(args.tools) do for _, schema in pairs(tool) do table.insert(transformed, schema) end end return { tools = transformed } end, }, tools = { format_calls = function(self, args) return args.tools end, format_response = function(self, args) return { role = self.roles.tool or "tool", tools = { call_id = adapter_utils.pairing_id(args.tool_call), name = args.tool_call["function"].name, }, content = args.output, opts = { visible = false }, } end, }, } ``` Many LLMs claim to follow OpenAI's function calling standard but still need adjustments to work. ## Schema The `schema` table describes the LLM's settings. With `display.chat.show_settings = true`, they appear at the top of the chat buffer for the user to edit. From the `openai_legacy` adapter: ```lua schema = { model = { order = 1, mapping = "parameters", type = "enum", desc = "ID of the model to use. See the model endpoint compatibility table for details on which models work with the Chat API.", default = "gpt-4.1", choices = { ["gpt-4.1"] = { formatted_name = "GPT 4.1", meta = { context_window = 1047576 }, opts = { has_vision = true, can_form_structured_outputs = true }, }, ["o3-mini-2025-01-31"] = { formatted_name = "o3 Mini", opts = { can_reason = true, can_form_structured_outputs = true }, }, "gpt-4", "gpt-3.5-turbo", }, }, }, ``` | Key | Description | | --- | --- | | `order` | Position in the chat buffer's settings | | `mapping` | Where in the adapter the value goes, such as `parameters` | | `type` | One of `string`, `number`, `integer`, `boolean`, `enum`, `list` or `map` | | `desc` | Shown as virtual text when the cursor is on the setting | | `default` | The default value, or a function that receives the adapter | | `choices` | The allowed values for an `enum`, or a function that returns them | | `optional` | Allow the value to be `nil` | | `enabled` | A function that receives the adapter and returns whether the setting applies | | `validate` | A function that returns whether the value is valid, and an error message if not | A choice can be a plain string, or a table with a `formatted_name`, `meta` and `opts` for your handlers to read at runtime. A setting can depend on the model: ```lua temperature = { order = 2, mapping = "parameters", type = "number", default = 0, enabled = function(self) local model = require("codecompanion.adapters.utils").resolve_model(self) return model ~= nil and not vim.startswith(model, "o1") end, validate = function(n) return n >= 0 and n <= 2, "Must be between 0 and 2" end, desc = "What sampling temperature to use, between 0 and 2.", }, ``` `o1` models don't accept a temperature, so `enabled` leaves it out of their requests. > \[!IMPORTANT] > `schema.model.default` can be a function. Use `resolve_model` from `codecompanion.adapters.utils` to read it as a string Some APIs, such as OpenAI's [Responses API](https://platform.openai.com/docs/api-reference/responses/create?api-mode=responses), nest parameters: ```bash curl https://api.openai.com/v1/responses \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -d '{ "model": "o3-mini", "input": "How much wood would a woodchuck chuck?", "reasoning": { "effort": "high" } }' ``` Use dot notation in the key to nest the value: ```lua ["reasoning.effort"] = { mapping = "parameters", type = "string", -- ... }, ``` ## Legacy Handlers Adapters written with the older flat handlers still work, and their handlers still take positional arguments. CodeCompanion detects the format and maps the old names to the new ones. From v20.0.0, the new handlers take a single `args` table. | Old | New | | --- | --- | | `setup`, `on_exit`, `teardown` | `lifecycle.setup`, `lifecycle.on_exit`, `lifecycle.teardown` | | `form_parameters`, `form_messages`, `form_tools` | `request.build_parameters`, `request.build_messages`, `request.build_tools` | | `form_structured_output`, `form_reasoning`, `set_body` | `request.build_structured_output`, `request.build_reasoning`, `request.build_body` | | `chat_output`, `inline_output`, `tokens` | `response.parse_chat`, `response.parse_inline`, `response.parse_tokens` | | `parse_message_meta` | `response.parse_meta` | | `tools.format_tool_calls`, `tools.output_response` | `tools.format_calls`, `tools.format_response` | **An adapter is treated as the new format if it has a `lifecycle`, `request` or `response` table**, so don't mix the two. To migrate, move each handler into its group and read its arguments from `args`: ::: code-group ```lua [Old] handlers = { setup = function(self) end, form_parameters = function(self, params, messages) end, form_messages = function(self, messages) end, chat_output = function(self, data, tools) end, on_exit = function(self, data) end, teardown = function(self) end, tools = { format_tool_calls = function(self, tools) end, output_response = function(self, tool_call, output) end, }, } ``` ```lua [New] handlers = { lifecycle = { setup = function(self) end, on_exit = function(self, args) end, teardown = function(self) end, }, request = { build_parameters = function(self, args) end, build_messages = function(self, args) end, }, response = { parse_chat = function(self, args) end, }, tools = { format_calls = function(self, args) end, format_response = function(self, args) end, }, } ``` ::: --- --- url: /extending/agentic-workflows.md description: >- Chain prompts with tool calls so an LLM can edit code, run your tests and keep fixing until they pass. --- # Extending with Agentic Workflows Some tasks take several turns, such as editing code, running the tests and fixing what fails, and prompting each turn by hand is slow. An *agentic workflow* is a [workflow](/usage/workflows) that combines a series of prompts with [tools](/usage/chat-buffer/agents-tools), so the LLM can carry out the loop for you. ## How They Work When you start a workflow from the [Action Palette](/usage/action-palette), the first group of prompts goes into a [chat buffer](/usage/chat-buffer/) and every later group is *subscribed* to it. Each time the LLM finishes a response and the chat is ready for your input, CodeCompanion checks the subscriptions in order: 1. A prompt with a `condition` that returns `false` stays queued for a later turn 2. Otherwise the prompt is added to the chat buffer, and sent if `opts.auto_submit` is `true` 3. A prompt with `repeat_until` stays subscribed and is added again on every turn until `repeat_until` returns `true`. Any other prompt is removed once it's been added Stopping or cancelling a request, or closing the chat buffer, stops any further prompts from being sent automatically. ## Prompt Fields Each prompt in a workflow can use: | Field | Description | | --- | --- | | `role` | `"user"` or `"system"` | | `content` | A string, or a function that receives the buffer context and returns one | | `name` | A label for the prompt, shown in the logs | | `opts.auto_submit` | Send the prompt as soon as it's added | | `opts.adapter` | Switch to this adapter and model, as `{ name = "copilot", model = "gpt-4.1" }`, when the prompt is added | | `condition` | A function that receives the chat buffer and returns whether the prompt can be added this turn | | `repeat_until` | A function that receives the chat buffer and returns `true` once the prompt should stop repeating | ## Edit and Test The `Edit<->Test` workflow originally came with the plugin. It asks the LLM to edit a buffer and run the test suite, then sends the failures back until the tests pass: ```lua require("codecompanion").setup({ prompt_library = { ["Edit<->Test workflow"] = { interaction = "chat", description = "Use a workflow to repeatedly edit then test code", opts = { approval_mode = "auto", is_workflow = true, }, prompts = { { { name = "Setup Test", role = "user", opts = { auto_submit = false }, content = function() return [[### Instructions Your instructions here ### Steps to Follow You are required to write code following the instructions provided above and test the correctness by running the designated test suite. Follow these steps exactly: 1. Update the code in #{buffer} using the @{edit_file} tool 2. Then use the @{run_command} tool to run the test suite with `` (do this after you have updated the code) 3. Make sure you trigger both tools in the same response We'll repeat this cycle until the tests pass. Ensure no deviations from these steps.]] end, }, }, { { name = "Repeat On Failure", role = "user", opts = { auto_submit = true }, condition = function(chat) return chat.tool_registry.flags.testing ~= nil end, repeat_until = function(chat) return chat.tool_registry.flags.testing == true end, content = "The tests have failed. Can you edit the buffer and run the test suite again?", }, }, }, }, }, }) ``` `approval_mode = "auto"` starts the chat in [Auto mode](/usage/chat-buffer/agents-tools#approval-modes), so the edits go ahead without asking. ### Setting the Task The first prompt sets the task and isn't sent automatically, so you can replace `Your instructions here` and `` before sending it. It gives the LLM the [edit\_file](/usage/chat-buffer/agents-tools#edit-file) and [run\_command](/usage/chat-buffer/agents-tools#run-command) tools, and shares the buffer with `#{buffer}`. The buffer is [synced](/usage/chat-buffer/editor-context#syncing), so the LLM sees each change it makes on the next turn. ### Repeating on Failure The second prompt drives the loop. The `run_command` tool asks the LLM to flag any command that runs a test suite. CodeCompanion then records whether the tests passed as `chat.tool_registry.flags.testing`: * **`condition`** - Waits until the LLM has run the tests, as the flag is `nil` before then * **`repeat_until`** - Sends the prompt again after every failing run, and stops once the tests pass * **`opts.auto_submit`** - Sends the prompt without waiting for you --- --- url: /extending/extensions.md description: >- Build a CodeCompanion extension, distributed as a Neovim plugin or defined in your own config. --- # Extending with Extensions An *extension* adds functionality to CodeCompanion, in the same way as extensions for telescope.nvim. Distribute one as a Neovim plugin or define it in your own config. ## Using Extensions Install the extension alongside CodeCompanion, then configure it under `extensions`: ```lua { "olimorris/codecompanion.nvim", dependencies = { "ravitemer/codecompanion-history.nvim", }, } ``` ```lua require("codecompanion").setup({ extensions = { history = { enabled = true, opts = { dir_to_save = vim.fn.stdpath("data") .. "/codecompanion_chats.json", }, }, }, }) ``` Extensions are enabled by default. Set `enabled = false` to skip loading one. See [Configuring Extensions](/configuration/extensions) for more. ## Creating Extensions A plugin extension lives under `lua/codecompanion/_extensions/`, in a directory named after the extension: ``` your-extension/ ├── lua/ │ └── codecompanion/ │ └── _extensions/ │ └── your_extension/ │ └── init.lua └── README.md ``` `init.lua` returns a table with a `setup` function and, optionally, `exports`: ```lua ---@class CodeCompanion.Extension ---@field setup fun(opts: table): any Function called when extension is loaded ---@field exports? table Optional table of functions exposed via codecompanion.extensions.name local Extension = {} ---@param opts table function Extension.setup(opts) -- Add keymaps, slash commands, tools etc. end Extension.exports = { clear_history = function() end, } return Extension ``` CodeCompanion calls `setup` with the extension's `opts` when you call `require("codecompanion").setup()`. ### Extending the Chat Buffer Extensions usually add keymaps, slash commands or tools to `require("codecompanion.config")` in `setup`. To add a chat buffer keymap: ```lua function Extension.setup(opts) local chat_keymaps = require("codecompanion.config").interactions.chat.keymaps chat_keymaps.open_saved_chats = { modes = { n = opts.keymap or "gh", }, description = "Open Saved Chats", callback = function(chat) vim.notify("Opening saved chats for " .. chat.id) end, } end ``` To reach a chat from elsewhere, use `require("codecompanion").last_chat()`. ### Exports Exports are available under the name the extension is configured with: ```lua require("codecompanion").extensions.history.clear_history() ``` ## Local Extensions To define an extension in your own config, pass it as a `callback`: ```lua require("codecompanion").setup({ extensions = { editor = { enabled = true, opts = {}, callback = { setup = function(opts) local chat_keymaps = require("codecompanion.config").interactions.chat.keymaps chat_keymaps.open_editor = { modes = { n = "ge", }, description = "Open Editor", callback = function(chat) vim.notify("Editor opened for chat " .. chat.id) end, } end, exports = { is_editor_open = function() return false end, }, }, }, }, }) ``` The `callback` can be: * The extension table * A function that returns the extension table * A module path that returns the extension, such as `"mcphub.extensions.codecompanion"` Without a `callback`, CodeCompanion loads `codecompanion._extensions.` from your runtimepath. ## Registering at Runtime To add an extension after setup: ```lua require("codecompanion").register_extension("history", { setup = function(opts) end, exports = {}, }) ``` The second argument is the extension table itself, not a `callback`. Its `setup` is called with an empty `opts` table. ## Best Practices * **Naming** - give your extension a unique name, and prefix its functions and variables to avoid clashes * **Configuration** - provide sensible defaults, take everything else through `opts` and document every option * **Integration** - follow CodeCompanion's patterns for keymaps, slash commands and tools, and handle errors with `pcall` * **Documentation** - cover installation, every option and some usage examples --- --- url: /extending/parsers.md description: >- Write your own rules parser to transform a rules file before it's shared with an LLM. --- # Extending with Rules Parsers A *parser* processes the contents of a [rules](/configuration/rules) file before it's shared with an LLM. It can rewrite the content, pull out a system prompt or add other files to the chat. ## Writing a Parser A parser is a module that returns a function. The function receives the rules file and returns a table with a `content` key: ```lua ---@param file CodeCompanion.Chat.Rules.ProcessedFile ---@return CodeCompanion.Chat.Rules.Parser return function(file) return { content = file.content or "" } end ``` The `file` table has: | Field | Description | | --- | --- | | `content` | The file's contents | | `path` | The file's full path, or relative to the current working directory if it's inside it | | `filename` | The file's name, without its directory | | `name` | The path as written in your rules config, or the full path for a file found in a directory | The parser can return: | Key | Description | | --- | --- | | `content` | The text to share with the LLM. Required | | `system_prompt` | Text to add as a system message | | `meta.included_files` | Files to add to the chat as context | If a parser errors, the file's original content is shared instead. ## Registering a Parser Add it under `rules.parsers`, as a module path or a function that returns the parser: ```lua require("codecompanion").setup({ rules = { parsers = { my_parser = "my_config.rules.my_parser", }, }, }) ``` Then apply it to a rules group or file by name, as shown in [Applying Parsers](/configuration/rules#applying-parsers). ## Including Files To add other files to the chat, return them in `meta.included_files`: ```lua { content = "Your parsed content", meta = { included_files = { ".codecompanion/acp/acp_json_schema.json", "./lua/codecompanion/acp/init.lua", "./lua/codecompanion/adapters/acp/claude_code.lua", }, }, } ``` Relative paths are resolved against the current working directory. A file that's open in a buffer is added as a buffer, and a file that's already in the chat isn't added again. The built-in `claude` parser does this for every line that starts with `@`, such as `@AGENTS.md`. --- --- url: /extending/tools.md description: >- Build your own tools that an LLM can call to run functions and shell commands in Neovim. --- # Extending with Tools A *tool* lets an LLM run a function or a shell command on your machine. In CodeCompanion, a tool is a Lua table made up of an [OpenAI compatible schema](https://platform.openai.com/docs/guides/function-calling?api-mode=chat), the commands to run, and handler and output functions that manage each call. When you add a tool to the chat buffer, the LLM receives its schema and can call it. CodeCompanion then runs the tool and sends the output back to the LLM. ## Architecture You don't need to understand the architecture to build a tool, but it shows when each of your functions is called: ```mermaid sequenceDiagram participant C as Chat Buffer participant L as LLM participant TS as Tool System participant O as Orchestrator participant T as Tool C->>L: Prompt with tool schemas L->>C: Response with tool call(s) C->>TS: Parse LLM response loop For each tool call detected TS->>TS: Tools.resolve(tool_config) TS->>TS: Add tool to queue end TS->>O: Create Orchestrator with queue TS->>C: Fire "ToolsStarted" autocmd loop While queue not empty O->>O: Pop tool from queue O->>T: handlers.setup() Note over O,C: If approval required, prompt user O->>C: User approval (if needed) Note over O,T: If rejected, call output.rejected and move to the next tool Note over O,T: If cancelled, call output.cancelled for every remaining tool and stop loop For each cmd in tool.cmds O->>T: Execute function(tools, args, opts) Note over T,O: Returns {status, data} (sync) or calls opts.output_cb (async) O->>T: output.success() OR output.error() T->>C: add_tool_output() Note over O,T: An error skips the tool's remaining cmds end O->>T: handlers.on_exit() end O->>C: Fire "ToolsFinished" autocmd C->>L: Agent loop sends the tools' output back ``` ## Building a Tool The tools live in `lua/codecompanion/interactions/chat/tools`: ``` interactions/chat/tools ├── init.lua ├── orchestrator.lua ├── runtime/ │ ├── queue.lua │ ├── runner.lua ├── builtin/ │ ├── cmd_tool.lua │ ├── run_command.lua │ ├── edit_file/ │ ├── create_file.lua │ ├── ... ``` When the LLM calls a tool, the chat buffer passes the call to the *tool system* in `tools/init.lua`. It resolves each tool and queues it for the *orchestrator*, which runs them one at a time. There are two types of tool: * **Command-based** - Run shell commands in the background with `vim.system`, so Neovim stays responsive. Suited to heavy or slow tasks * **Function-based** - Run Lua functions in the main Neovim process, one after another, like [edit\_file](https://github.com/olimorris/codecompanion.nvim/blob/main/lua/codecompanion/interactions/chat/tools/builtin/edit_file/init.lua). They can also run asynchronously The rest of this section builds a function-based calculator that an LLM can use for basic maths. ### Structure A tool is a table with these fields: | Field | Type | Description | | --- | --- | --- | | `name` | `string` | The tool's name, matching the schema's function name and its key in the config | | `kind` | `string` | How the tool is labelled in the chat buffer: `read`, `edit`, `delete`, `move`, `search`, `execute`, `think`, `fetch` or `other`. The tool's name is used when it's left out | | `schema` | `table` | The schema the LLM follows to call the tool | | `cmds` | `table` | The commands or functions to run, in order | | `system_prompt` | `string\|fun(schema)` | Extra instructions for the LLM | | `env` | `fun(tool): table` | Values to substitute into `${}` placeholders in `cmds` | | `handlers` | `table` | `setup`, `prompt_condition` and `on_exit` | | `output` | `table` | `success`, `error`, `prompt`, `rejected`, `cancelled` and `cmd_string` | | `gates` | `table` | `is_safe` and `judge_context`, for [Auto mode](/usage/chat-buffer/agents-tools#approval-modes) | | `opts` | `table` | The tool's [options](#options), merged with `opts` from its config entry | CodeCompanion sets two more fields when the LLM calls the tool: `args`, the decoded arguments, and `function_call`, the raw tool call. ### `cmds` **Command-based tools** Each command runs one after another, through the shell, using `vim.system`: ```lua cmds = { { "make", "test" }, { "echo", "hello" }, } ``` A command can be a table of arguments, a string such as `"make test"`, or a table with a `cmd` and a `flag`. A `flag` records whether the command succeeded in `chat.tool_registry.flags`, which [agentic workflows](/extending/agentic-workflows) can check. A command that exits with a non-zero code is an error, with its stderr and stdout as the output. Set `opts.timeout` in milliseconds to stop a long-running command. To use values from the LLM's arguments in a command, return them from `env` and reference them with `${}`. The `env` function receives a copy of the tool, so `tool.args` holds the arguments: ```lua cmds = { { "docker", "pull", "${lang}" }, { "docker", "run", "--rm", "-v", "${temp_dir}:${temp_dir}", "${lang}", "${lang}", "${temp_input}" }, }, ---@param tool CodeCompanion.Tools.Tool ---@return table env = function(tool) local temp_input = vim.fn.tempname() return { lang = tool.args.lang, temp_dir = vim.fs.dirname(temp_input), temp_input = temp_input, } end, ``` > \[!TIP] > To build commands from the LLM's arguments, insert them into `self.cmds` from `handlers.setup`, as the [run\_command](https://github.com/olimorris/codecompanion.nvim/blob/main/lua/codecompanion/interactions/chat/tools/builtin/run_command.lua) tool does **Function-based tools** Each function receives three arguments: * **`self`** - The tool system. The running tool is `self.tool` and the chat buffer is `self.chat` * **`args`** - The arguments from the LLM's tool call * **`opts`** - A table with `input`, the result of the previous function, `output_cb` for asynchronous results and `register_job` for a running `vim.SystemObj` A synchronous function returns a table with a `status` of `"success"` or `"error"`, and its `data`. For the calculator: ```lua cmds = { ---@param self CodeCompanion.Tools ---@param args table ---@param opts { input: any, output_cb: fun(result: table), register_job: fun(job: vim.SystemObj) } ---@return nil|{ status: "success"|"error", data: any } function(self, args, opts) local num1 = tonumber(args.num1) local num2 = tonumber(args.num2) local operation = args.operation if not num1 then return { status = "error", data = "First number is missing or invalid" } end if not num2 then return { status = "error", data = "Second number is missing or invalid" } end if not operation then return { status = "error", data = "Operation is missing" } end local result if operation == "add" then result = num1 + num2 elseif operation == "subtract" then result = num1 - num2 elseif operation == "multiply" then result = num1 * num2 elseif operation == "divide" then if num2 == 0 then return { status = "error", data = "Cannot divide by zero" } end result = num1 / num2 else return { status = "error", data = "Invalid operation: must be add, subtract, multiply, or divide" } end return { status = "success", data = result } end, }, ``` A `"success"` result calls `output.success` and moves on to the next function. An `"error"` result calls `output.error` and skips the tool's remaining functions. A function that throws a Lua error is treated in the same way. An asynchronous function returns nothing and passes its result to `opts.output_cb` instead. Register any job with `opts.register_job` so stopping the chat can kill it: ```lua cmds = { function(self, args, opts) local output_cb = vim.schedule_wrap(opts.output_cb) local job = vim.system({ "curl", "-sL", args.url }, { text = true }, function(out) if out.code ~= 0 then return output_cb({ status = "error", data = out.stderr }) end output_cb({ status = "success", data = out.stdout }) end) opts.register_job(job) end, }, ``` The `vim.system` callback runs outside the main loop, which is why `output_cb` is wrapped in `vim.schedule_wrap`. Only the first call to `output_cb` counts, and a function must either return a result or call `output_cb`, never both. ### `schema` The schema is the structure the LLM follows to call the tool. CodeCompanion decodes the LLM's call with `vim.json.decode` and passes it to each function in [cmds](#cmds) as `args`. The calculator takes two numbers and an operation: ```lua schema = { type = "function", ["function"] = { name = "calculator", description = "Perform simple mathematical operations on a user's machine", parameters = { type = "object", properties = { num1 = { type = "integer", description = "The first number in the calculation", }, num2 = { type = "integer", description = "The second number in the calculation", }, operation = { type = "string", enum = { "add", "subtract", "multiply", "divide" }, description = "The mathematical operation to perform on the two numbers", }, }, required = { "num1", "num2", "operation", }, additionalProperties = false, }, strict = true, }, }, ``` ### `system_prompt` The schema is usually enough for the LLM to use a tool. A complicated tool can add a system prompt, as the `memory` tool does. It's a string, or a function that receives the tool's schema. > \[!TIP] > Use a system prompt sparingly. Needing one is often a sign that the tool should be split into several tools To show how it works, the calculator adds one: ```lua system_prompt = [[## Calculator Tool (`calculator`) ## CONTEXT - You have access to a calculator tool running within CodeCompanion, in Neovim. - You can use it to add, subtract, multiply or divide two numbers. ### OBJECTIVE - Do a mathematical operation on two numbers when the user asks ### RESPONSE - Always use the structure above for consistency. ]], ``` ### `handlers` Each handler receives the tool as `self` and a `meta` table with the tool system as `meta.tools`: | Handler | Called | | --- | --- | | `setup` | Before approval and before anything in [cmds](#cmds). Use it to build `cmds` dynamically | | `prompt_condition` | Before approval, when `opts.require_approval_before` isn't a boolean. Returns whether to ask the user | | `on_exit` | After the tool's last function, after an error, or when you cancel the tool | `edit_file` uses `prompt_condition` to read `require_approval_before = { buffer = false, file = false }` and ask only for the kind of edit you've chosen. The calculator sends notifications so you can follow the flow: ```lua handlers = { ---@param self CodeCompanion.Tools.Tool ---@param meta { tools: CodeCompanion.Tools } setup = function(self, meta) return vim.notify("setup function called", vim.log.levels.INFO) end, ---@param self CodeCompanion.Tools.Tool ---@param meta { tools: CodeCompanion.Tools } on_exit = function(self, meta) return vim.notify("on_exit function called", vim.log.levels.INFO) end, }, ``` > \[!TIP] > The chat buffer is `meta.tools.chat` in every handler and output function ### `output` Output functions turn the results of [cmds](#cmds) into messages for the LLM and the user. Each receives the tool as `self`: | Function | Signature | Called | | --- | --- | --- | | `success` | `(self, stdout, meta)` | After every successful function | | `error` | `(self, stderr, meta)` | Once, when a function fails | | `prompt` | `(self, meta)` | When approval is needed. Returns the question to ask | | `rejected` | `(self, meta)` | When you reject the tool. `meta.opts.reason` holds your reason | | `cancelled` | `(self, meta)` | When you cancel the tool, or it's still queued when you cancel another | | `cmd_string` | `(self, meta)` | When labelling the tool in the chat buffer. Returns a string | `stdout` is a list of the `data` from each of the tool's successful functions so far, and `stderr` is a list of the errors collected this turn, with the latest last. Both are `nil` when empty. `meta.tools` is the tool system. For the calculator: ```lua output = { ---@param self CodeCompanion.Tools.Tool ---@param stdout table ---@param meta { tools: CodeCompanion.Tools, cmd: table } success = function(self, stdout, meta) return meta.tools.chat:add_tool_output(self, { content = tostring(stdout[#stdout]) }) end, ---@param self CodeCompanion.Tools.Tool ---@param stderr table ---@param meta { tools: CodeCompanion.Tools, cmd: table } error = function(self, stderr, meta) return meta.tools.chat:add_tool_output(self, { content = tostring(stderr[#stderr]) }) end, }, ``` `add_tool_output` adds the tool's output to the chat's message history: ```lua meta.tools.chat:add_tool_output(self, { content = "The result of 6 * 7 is 42", }) ``` The first argument is the tool that ran, `self` in an output function. `content` is sent to the LLM. The chat buffer shows only the tool's summary line, and `K` on that line shows the full call and its output. > \[!IMPORTANT] > If an output function you define doesn't call `add_tool_output`, the LLM receives a stand-in result saying the tool call didn't complete Leave an output function out and CodeCompanion uses a default: | Function | Default | | --- | --- | | `success` | `Executed` and the tool's name | | `error` | `Error calling` and the tool's name | | `prompt` | The tool call's summary line | | `rejected` | The user rejected the tool, with their reason | | `cancelled` | The user cancelled the tool | | `cmd_string` | The tool's name alone | ### Running the Calculator Putting it all together: ```lua require("codecompanion").setup({ interactions = { chat = { tools = { calculator = { description = "Perform calculations", name = "calculator", cmds = { ---@param self CodeCompanion.Tools ---@param args table ---@param opts { input: any, output_cb: fun(result: table), register_job: fun(job: vim.SystemObj) } ---@return nil|{ status: "success"|"error", data: any } function(self, args, opts) local num1 = tonumber(args.num1) local num2 = tonumber(args.num2) local operation = args.operation if not num1 then return { status = "error", data = "First number is missing or invalid" } end if not num2 then return { status = "error", data = "Second number is missing or invalid" } end if not operation then return { status = "error", data = "Operation is missing" } end local result if operation == "add" then result = num1 + num2 elseif operation == "subtract" then result = num1 - num2 elseif operation == "multiply" then result = num1 * num2 elseif operation == "divide" then if num2 == 0 then return { status = "error", data = "Cannot divide by zero" } end result = num1 / num2 else return { status = "error", data = "Invalid operation: must be add, subtract, multiply, or divide", } end return { status = "success", data = result } end, }, system_prompt = [[## Calculator Tool (`calculator`) ## CONTEXT - You have access to a calculator tool running within CodeCompanion, in Neovim. - You can use it to add, subtract, multiply or divide two numbers. ### OBJECTIVE - Do a mathematical operation on two numbers when the user asks ### RESPONSE - Always use the structure above for consistency. ]], schema = { type = "function", ["function"] = { name = "calculator", description = "Perform simple mathematical operations on a user's machine", parameters = { type = "object", properties = { num1 = { type = "integer", description = "The first number in the calculation", }, num2 = { type = "integer", description = "The second number in the calculation", }, operation = { type = "string", enum = { "add", "subtract", "multiply", "divide" }, description = "The mathematical operation to perform on the two numbers", }, }, required = { "num1", "num2", "operation", }, additionalProperties = false, }, strict = true, }, }, handlers = { ---@param self CodeCompanion.Tools.Tool ---@param meta { tools: CodeCompanion.Tools } setup = function(self, meta) return vim.notify("setup function called", vim.log.levels.INFO) end, ---@param self CodeCompanion.Tools.Tool ---@param meta { tools: CodeCompanion.Tools } on_exit = function(self, meta) return vim.notify("on_exit function called", vim.log.levels.INFO) end, }, output = { ---@param self CodeCompanion.Tools.Tool ---@param stdout table ---@param meta { tools: CodeCompanion.Tools, cmd: table } success = function(self, stdout, meta) return meta.tools.chat:add_tool_output(self, { content = tostring(stdout[#stdout]) }) end, ---@param self CodeCompanion.Tools.Tool ---@param stderr table ---@param meta { tools: CodeCompanion.Tools, cmd: table } error = function(self, stderr, meta) return meta.tools.chat:add_tool_output(self, { content = tostring(stderr[#stderr]) }) end, }, }, }, }, }, }) ``` Then, in a chat buffer: ``` Use the @{calculator} tool for 100*50 ``` The chat buffer shows `5000`. The tool can live in its own file instead. Point `path` at a Lua module or a file path that returns the tool table, or set `callback` to a function that returns it: ```lua require("codecompanion").setup({ interactions = { chat = { tools = { calculator = { description = "Perform calculations", path = "user.tools.calculator", }, }, }, }, }) ``` ## Approvals An LLM can call a tool in ways you didn't expect. To have CodeCompanion ask you before a tool runs, set `require_approval_before` in the tool's `opts`: ```lua require("codecompanion").setup({ interactions = { chat = { tools = { calculator = { description = "Perform calculations", path = "user.tools.calculator", opts = { require_approval_before = true, }, }, }, }, }, }) ``` `require_approval_before` can also be a function that receives the tool and the tool system, and returns a boolean. For any other value, such as a table, `handlers.prompt_condition` decides. Add `output.prompt` to word the question: ```lua output = { ---@param self CodeCompanion.Tools.Tool ---@param meta { tools: CodeCompanion.Tools } ---@return string prompt = function(self, meta) return string.format("Perform the calculation `%s %s %s`?", self.args.num1, self.args.operation, self.args.num2) end, }, ``` This asks `Perform the calculation 100 multiply 50?`, and you can always accept, accept, reject or cancel. Rejecting asks for an optional reason, which is sent to the LLM, and moves on to the next tool. Cancelling stops every remaining tool. To change what the LLM is told in either case: ```lua output = { ---@param self CodeCompanion.Tools.Tool ---@param meta { tools: CodeCompanion.Tools, cmd: table, opts: { reason?: string } } ---@return nil rejected = function(self, meta) meta.tools.chat:add_tool_output(self, { content = "The user declined to run the calculator tool" }) end, ---@param self CodeCompanion.Tools.Tool ---@param meta { tools: CodeCompanion.Tools, cmd: table } ---@return nil cancelled = function(self, meta) meta.tools.chat:add_tool_output(self, { content = "The user cancelled the execution of the calculator tool" }) end, }, ``` ### Auto Mode In [Auto mode](/usage/chat-buffer/agents-tools#approval-modes), tools run without asking. A tool with `protect` or `require_cmd_approval` set in its config entry still goes through approval, where two `gates` can let it run without asking: | Gate | Description | | --- | --- | | `is_safe(self, meta)` | Returns `true` to run the tool without asking, as `run_command` does for its `safe_commands` | | `judge_context(self, meta)` | Returns a plain English description of the call for the [LLM judge](/configuration/tools#llm-judge) to vet | `protect` turns both gates off, so the tool always asks. The judge only runs when it's enabled and the tool sets `opts.judge = true`. `require_cmd_approval` makes **Always accept** apply to a single command, as returned by `output.cmd_string`, rather than the whole tool. ## Extending `cmd_tool` Most custom tools run a command on your machine, and the handlers and output functions in [run\_command](/usage/chat-buffer/agents-tools#run-command) already cover that. Set `extends = "cmd_tool"` to reuse them, and provide: | Field | Description | | --- | --- | | `name` | The tool's name, matching its key in the config | | `description` | What the tool does, sent to the LLM in the schema | | `schema` | The schema's `properties` and `required`, with `additionalProperties` optional | | `build_cmd` | A function that receives the LLM's arguments and returns the command to run, as a string | | `system_prompt` | Optional instructions for the LLM | | `kind` | Optional. How the tool is labelled in the chat buffer, defaulting to `execute` | `cmd_tool` wraps `schema` in a full schema, runs the command from `build_cmd` and shows it in the approval prompt. Your own `handlers` and `output` functions replace the matching defaults, and `gates` are passed through. `cmd_tool` doesn't carry over `opts` from the tool table, so set them on the config entry. This wraps the [beads](https://github.com/steveyegge/beads) CLI: ```lua require("codecompanion").setup({ interactions = { chat = { tools = { ["beads"] = { extends = "cmd_tool", description = "Beads task management", opts = { require_approval_before = true }, name = "beads", system_prompt = [[Beads is a local, hash-based task tracking system. Tasks have short IDs like `bd-a1b2`. Key commands: - `bd ready` - list tasks with no open blockers (i.e. ready to work on) - `bd show ` - show full details for a task - `bd create "" -p <priority>` - create a new task (priority 0 = highest) - `bd update <id> --claim` - assign a task to yourself - `bd update <id> --status done` - mark a task as done - `bd dep add <child> <parent>` - make child depend on parent Output is JSON. Always use `bd ready` first to see what's available before taking action.]], schema = { properties = { action = { type = "string", enum = { "ready", "show", "create", "update", "dep" }, description = "The beads action to perform", }, task_id = { type = "string", description = "The task ID (e.g. bd-a1b2). Required for show, update, and dep actions", }, args = { type = "string", description = "Additional arguments for the command (e.g. title for create, flags for update)", }, }, required = { "action" }, }, build_cmd = function(args) local parts = { "bd", args.action } if args.task_id then table.insert(parts, args.task_id) end if args.args then table.insert(parts, args.args) end return table.concat(parts, " ") end, }, }, }, }, }) ``` To keep the tool in its own file, point `path` at it in your config: ```lua require("codecompanion").setup({ interactions = { chat = { tools = { ["beads"] = { description = "Beads task management", opts = { require_approval_before = true }, path = "~/.dotfiles/.config/tools/beads.lua", }, }, }, }, }) ``` Then return the tool from that file: ```lua -- ~/.dotfiles/.config/tools/beads.lua return { extends = "cmd_tool", name = "beads", description = "Manage tasks using the Beads task tracking system (bd CLI)", system_prompt = [[...]], schema = { ... }, build_cmd = function(args) local parts = { "bd", args.action } if args.task_id then table.insert(parts, args.task_id) end if args.args then table.insert(parts, args.args) end return table.concat(parts, " ") end, } ``` The `schema` limits what the LLM can pass to `build_cmd`, and the `system_prompt` tells it what each beads command does so it can choose the right action. ## Adapter Tools Some providers, such as [Anthropic](https://docs.claude.com/en/docs/agents-and-tools/tool-use/computer-use-tool) and [OpenAI](https://platform.openai.com/docs/guides/tools-web-search?api-mode=responses), run their own tools that CodeCompanion can enable. PR [#2307](https://github.com/olimorris/codecompanion.nvim/pull/2307) added them to the Anthropic and OpenAI adapters. To support one in an adapter: 1. Add the tool to the adapter's `available_tools`: ```lua -- openai.lua available_tools = { ["web_search"] = { description = "Allow models to search the web for the latest information before generating a response.", enabled = true, ---@param self CodeCompanion.HTTPAdapter ---@param meta { tools: table } callback = function(self, meta) table.insert(meta.tools, { type = "web_search", }) end, }, }, ``` The `callback` can also change the adapter for the tool to work. Anthropic's tools add a beta header, for example. An entry can also set a `system_prompt`, and `enabled` can be a function that receives the adapter. 2. In the adapter's `handlers.request.build_tools`, call the `callback` for any adapter tool: ```lua build_tools = function(self, args) local tools = args.tools local transformed = {} for _, tool in pairs(tools) do for _, schema in pairs(tool) do if schema._meta and schema._meta.adapter_tool then if self.available_tools[schema.name] then self.available_tools[schema.name].callback(self, { tools = transformed }) end else -- Transform the schema as normal end end end return { tools = transformed } end, ``` Some adapter tools are *hybrid*, with a client-side part that CodeCompanion runs, like Anthropic's [memory](/usage/chat-buffer/agents-tools#memory) tool. Set `opts.client_tool` to the path of the built-in tool's entry in the config: ```lua ["memory"] = { -- ... opts = { client_tool = "interactions.chat.tools.memory", }, }, ``` ## Options Options go in the tool's `opts`, or in `opts` on its config entry, which takes precedence: | Option | Description | | --- | --- | | `require_approval_before` | Ask before the tool runs. A boolean, a function or a value for `handlers.prompt_condition` | | `require_cmd_approval` | Make **Always accept** apply to one command at a time. Config entry only | | `protect` | Always ask in Auto mode. Config entry only | | `judge` | Let the LLM judge vet the tool in Auto mode | | `timeout` | Stop a command-based tool's commands after this many milliseconds | | `use_handlers_once` | Run `setup` and `on_exit` once when the LLM calls the tool several times in a row. Calls after the first skip approval | `use_handlers_once` is for a tool the LLM often calls several times in one response, such as an editor: ```lua return { name = "editor", opts = { use_handlers_once = true, }, -- ... } ``` ## Limitations * Tools only work with HTTP adapters. ACP adapters bring their own tools * Function-based tools run in the main Neovim process, so a slow synchronous function blocks the editor until it returns --- --- url: /extending/ui.md description: >- Recipes for showing CodeCompanion's progress and chat metadata in Neovim with Fidget.nvim, lualine.nvim and heirline.nvim. --- # Extending with UI Recipes CodeCompanion fires [events](/usage/events) and exposes [chat metadata](/usage/ui) rather than changing your UI itself. These recipes, many from the community, use them to show progress and chat details in Neovim. ## Fidget.nvim Progress Updates By [@jessevdp](https://github.com/jessevdp). See discussion [#813](https://github.com/olimorris/codecompanion.nvim/discussions/813) for the code. ## Fidget.nvim Inline Spinner By [@yuhua99](https://github.com/yuhua99). See [this comment](https://github.com/olimorris/codecompanion.nvim/discussions/640#discussioncomment-12866279) on discussion #640 for the code. ## Status Column Extmarks By [@lucobellic](https://github.com/lucobellic), for the inline interaction. See discussion [#1297](https://github.com/olimorris/codecompanion.nvim/discussions/1297) for the code. ## Lualine.nvim To show a spinner in [lualine.nvim](https://github.com/nvim-lualine/lualine.nvim) while a request is running: ```lua local M = require("lualine.component"):extend() M.processing = false M.spinner_index = 1 local spinner_symbols = { "⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏", } local spinner_symbols_len = 10 -- Initializer function M:init(options) M.super.init(self, options) local group = vim.api.nvim_create_augroup("CodeCompanionHooks", {}) vim.api.nvim_create_autocmd({ "User" }, { pattern = "CodeCompanionRequest*", group = group, callback = function(request) if request.match == "CodeCompanionRequestStarted" then self.processing = true elseif request.match == "CodeCompanionRequestFinished" then self.processing = false end end, }) end -- Function that runs every time statusline is updated function M:update_status() if self.processing then self.spinner_index = (self.spinner_index % spinner_symbols_len) + 1 return spinner_symbols[self.spinner_index] else return nil end end return M ``` ## Heirline.nvim The first video on this page shows this recipe alongside the Fidget.nvim progress updates. To show an icon in [heirline.nvim](https://github.com/rebelot/heirline.nvim) while a request is running, along with the buffer that `#{buffer}` shares and the chat's token and cycle counts: ```lua local CodeCompanion = { static = { processing = false, }, update = { "User", pattern = "CodeCompanionRequest*", callback = function(self, args) if args.match == "CodeCompanionRequestStarted" then self.processing = true elseif args.match == "CodeCompanionRequestFinished" then self.processing = false end vim.cmd("redrawstatus") end, }, { condition = function(self) return self.processing end, provider = " ", hl = { fg = "yellow" }, }, } local IsCodeCompanion = function() return package.loaded.codecompanion and vim.bo.filetype == "codecompanion" end local CodeCompanionCurrentContext = { static = { enabled = true, }, condition = function(self) return IsCodeCompanion() and _G.codecompanion_current_context ~= nil and self.enabled end, provider = function() local bufname = vim.fn.fnamemodify(vim.api.nvim_buf_get_name(_G.codecompanion_current_context), ":t") return "[  " .. bufname .. " ] " end, hl = { fg = "gray", bg = "bg" }, update = { "User", pattern = { "CodeCompanionRequest*", "CodeCompanionContextChanged" }, callback = vim.schedule_wrap(function(self, args) if args.match == "CodeCompanionRequestStarted" then self.enabled = false elseif args.match == "CodeCompanionRequestFinished" then self.enabled = true end vim.cmd("redrawstatus") end), }, } local CodeCompanionStats = { condition = function(self) return IsCodeCompanion() end, static = { chat_values = {}, }, init = function(self) local bufnr = vim.api.nvim_get_current_buf() self.chat_values = _G.codecompanion_chat_metadata[bufnr] end, -- Tokens block { condition = function(self) return self.chat_values.tokens > 0 end, RightSlantStart, { provider = function(self) return "  " .. self.chat_values.tokens .. " " end, hl = { fg = "gray", bg = "statusline_bg" }, update = { "User", pattern = { "CodeCompanionChatOpened", "CodeCompanionRequestFinished" }, callback = vim.schedule_wrap(function() vim.cmd("redrawstatus") end), }, }, RightSlantEnd, }, -- Cycles block { condition = function(self) return self.chat_values.cycles > 0 end, RightSlantStart, { provider = function(self) return "  " .. self.chat_values.cycles .. " " end, hl = { fg = "gray", bg = "statusline_bg" }, update = { "User", pattern = { "CodeCompanionChatOpened", "CodeCompanionRequestFinished" }, callback = vim.schedule_wrap(function() vim.cmd("redrawstatus") end), }, }, RightSlantEnd, }, } ``` `RightSlantStart` and `RightSlantEnd` are separator components from your own heirline.nvim config. --- --- url: /commands.md description: Look up every CodeCompanion command and the arguments each one takes. --- # Commands CodeCompanion has six commands: one for each interaction, plus the action palette and code reviews. None are mapped to keys by default. ## Chat | Command | Description | |---|---| | `:CodeCompanionChat` | Open a new chat buffer | | `:CodeCompanionChat <prompt>` | Open a chat buffer and send the prompt | | `:CodeCompanionChat adapter=<adapter> model=<model>` | Open a chat buffer with a specific HTTP adapter and model | | `:CodeCompanionChat adapter=<adapter> command=<command>` | Open a chat buffer with a specific ACP adapter and command | | `:CodeCompanionChat Toggle` | Show or hide the last chat buffer, creating one if none exist | | `:CodeCompanionChat Add` | Add the visual selection to the last chat buffer, creating one if none exist | | `:CodeCompanionChat Changes` | Open every file the LLM has changed in the quickfix list | | `:CodeCompanionChat RefreshCache` | Re-check which conditionally enabled tools and slash commands are available | `adapter=` and `model=` can be used on their own, and alongside a prompt. ## Inline | Command | Description | |---|---| | `:CodeCompanion` | Ask for a prompt, then send it to the inline interaction | | `:CodeCompanion <prompt>` | Send the prompt to the inline interaction | | `:CodeCompanion adapter=<adapter> <prompt>` | Send the prompt with a specific HTTP adapter | | `:CodeCompanion /<alias> <prompt>` | Run a [prompt library](/usage/prompt-library) item by its alias, with an optional prompt of your own | ## CLI | Command | Description | |---|---| | `:CodeCompanionCLI` | Open a new CLI interaction, or send the visual selection to the last one | | `:CodeCompanionCLI <prompt>` | Send the prompt to the last CLI interaction, creating one if none exist | | `:CodeCompanionCLI! <prompt>` | Send the prompt and submit it | | `:CodeCompanionCLI agent=<agent>` | Open a new CLI interaction with a specific agent | | `:CodeCompanionCLI agent=<agent> <prompt>` | Send the prompt to that agent's CLI interaction, creating one if none exist | | `:CodeCompanionCLI Ask` | Write the prompt in a buffer with editor context, then save to send it | | `:CodeCompanionCLI Install` | Write CodeCompanion's hooks into your CLI agents' settings | ## Code Review | Command | Description | |---|---| | `:CodeCompanionCodeReview` | Open the changes made since the last review in the [review window](/usage/code-review) | | `:CodeCompanionCodeReview Branch` | Review every change on the branch since it left the default branch | | `:CodeCompanionCodeReview Comment` | Comment on the current line or visual selection | ## Others | Command | Description | |---|---| | `:CodeCompanionActions` | Open the [action palette](/usage/action-palette), or the chat's own palette from a chat buffer | | `:CodeCompanionActions Refresh` | Reload the action palette and prompt library, then open it | | `:CodeCompanionCmd <prompt>` | Generate a command for the command-line | --- --- url: /keymaps.md description: >- The default keymaps in CodeCompanion's chat buffer, inline diff and code review window. --- # Keymaps > \[!WARNING] > Not written yet --- --- url: /usage/events.md description: >- Every event CodeCompanion fires, and how to hook into them from your Neovim config. --- # Events CodeCompanion fires `User` autocmds at points in its lifecycle, so your config can react to a chat opening, a request finishing or a file being edited. For hooks that can change a chat's state, such as `on_before_submit` and `on_tool_output`, see [Callbacks](/configuration/callbacks). ## Consuming an Event To format a buffer once you've kept an inline edit: ```lua local group = vim.api.nvim_create_augroup("CodeCompanionHooks", {}) vim.api.nvim_create_autocmd({ "User" }, { pattern = "CodeCompanionInline*", group = group, callback = function(request) if request.match == "CodeCompanionInlineAccepted" then require("conform").format({ bufnr = request.buf }) end end, }) ``` ## Event Data Each event carries a `data` payload. For `CodeCompanionRequestStarted`: ```lua { buf = 10, data = { adapter = { formatted_name = "Copilot", model = "o3-mini-2025-01-31", name = "copilot" }, bufnr = 10, id = 6107753, interaction = "chat" }, event = "User", file = "CodeCompanionRequestStarted", group = 14, id = 30, match = "CodeCompanionRequestStarted" } ``` `CodeCompanionRequestFinished` adds a `status`, such as `"success"`, `"error"` or `"cancelled"`. ## Triggering an Event To make the chat buffer re-check which tools and slash commands are enabled: ```lua vim.api.nvim_exec_autocmds("User", { pattern = "CodeCompanionChatRefreshCache", }) ``` ## List of Events **Chat buffer** | Event | Fired | | --- | --- | | `CodeCompanionChatCreated` | After a chat is created for the first time | | `CodeCompanionChatOpened` | After a chat is opened | | `CodeCompanionChatHidden` | After a chat is hidden | | `CodeCompanionChatClosed` | After a chat is closed for good | | `CodeCompanionChatSubmitted` | After a message is sent | | `CodeCompanionChatDone` | After a response is received | | `CodeCompanionChatStopped` | After a request is stopped | | `CodeCompanionChatCleared` | After a chat is cleared | | `CodeCompanionChatRestored` | After a chat is made editable again, such as when `on_before_submit` blocks a message | | `CodeCompanionChatCompacting` | When a chat starts compacting its messages | | `CodeCompanionChatAdapter` | After the adapter is set | | `CodeCompanionChatModel` | After the model is set | | `CodeCompanionChatTitle` | After the title is set, with the `title` in the payload | | `CodeCompanionChatToolAdded` | After a tool is added, with the `tool` in the payload | **Sessions** | Event | Fired | | --- | --- | | `CodeCompanionChatSessionSaved` | After a chat is saved to disk, with its `slug` in the payload | | `CodeCompanionChatSessionRestored` | After a session is restored, with its `stem` in the payload | | `CodeCompanionChatSessionsChanged` | After a session is written or deleted | **ACP** | Event | Fired | | --- | --- | | `CodeCompanionACPConnected` | After the connection is authenticated and ready | | `CodeCompanionACPSessionPre` | After authentication, before a session starts, so you can change the connection, such as adding MCP servers | | `CodeCompanionACPSessionPost` | After a session starts | | `CodeCompanionACPChatRestored` | After a session is restored | | `CodeCompanionACPCommandsUpdate` | After an agent's commands are loaded, with the `commands` in the payload | | `CodeCompanionChatACPConfigChanged` | After an agent's configuration options change, with the `config_options` in the payload | **CLI** | Event | Fired | | --- | --- | | `CodeCompanionCLICreated` | After a CLI buffer is created for the first time | | `CodeCompanionCLIOpened` | After a CLI buffer is opened | | `CodeCompanionCLIHidden` | After a CLI buffer is hidden | | `CodeCompanionCLIClosed` | After a CLI buffer is closed | | `CodeCompanionCLISent` | After text is sent to a CLI buffer | | `CodeCompanionCLISubmitted` | When the agent accepts a prompt, however it was typed | | `CodeCompanionCLIDone` | When the agent finishes a turn | | `CodeCompanionCLIApprovalRequested` | When the agent is waiting on you, with a `message` in the payload | | `CodeCompanionCLIApprovalFinished` | When the agent resumes after waiting | The last four need [agent hooks](/configuration/cli#hooks). **Requests** | Event | Fired | | --- | --- | | `CodeCompanionRequestStarted` | At the start of a request, or a CLI agent's turn when [hooks](/configuration/cli#hooks) are set up | | `CodeCompanionRequestStreaming` | At the start of a streaming request | | `CodeCompanionRequestFinished` | At the end of a request, or a CLI agent's turn when [hooks](/configuration/cli#hooks) are set up | | `CodeCompanionInlineStarted` | At the start of an inline request | | `CodeCompanionInlineFinished` | At the end of an inline request, before its edits are reviewed | | `CodeCompanionInlineAccepted` | When you keep any of an inline request's edits | **Tools** | Event | Fired | | --- | --- | | `CodeCompanionToolsStarted` | When a batch of tools starts | | `CodeCompanionToolsFinished` | When a batch of tools finishes, is stopped or is cancelled | | `CodeCompanionToolStarted` | When a tool starts | | `CodeCompanionToolFinished` | When a tool finishes | | `CodeCompanionToolApprovalRequested` | When a tool asks for approval | | `CodeCompanionToolApprovalFinished` | When you approve or reject a tool | | `CodeCompanionToolQuestionAsked` | When a tool asks you a question | | `CodeCompanionToolQuestionAnswered` | When you answer or skip a question | | `CodeCompanionToolsJudgeStarted` | When the [judge](/guides/background-model#tool-judge) starts vetting a tool call | | `CodeCompanionToolsJudgeFinished` | When the judge returns its verdict | **Files and diffs** | Event | Fired | | --- | --- | | `CodeCompanionFileEdited` | After a file is edited or created, with the `path` and the `tool` that changed it in the payload | | `CodeCompanionDiffHunkChanged` | After moving to the next or previous hunk in a diff | **MCP** | Event | Fired | | --- | --- | | `CodeCompanionMCPServerStart` | When a server starts | | `CodeCompanionMCPServerReady` | When a server is ready for requests | | `CodeCompanionMCPServerToolsLoaded` | When a server's tools are loaded | | `CodeCompanionMCPServerClosed` | When a server closes | --- --- url: /architecture.md description: >- Understand how CodeCompanion keeps a conversation inside the LLM's context window, for contributors and curious users. --- # Architecture This page covers how parts of CodeCompanion work under the hood. You don't need it to use the plugin, but it's useful if you're contributing or want to know why CodeCompanion behaves the way it does. ## How Context Is Managed An LLM can only consider a limited number of [tokens](https://platform.claude.com/docs/en/about-claude/glossary#tokens) at once, known as its *context window*. When a conversation outgrows it, the conversation ends and can't continue, which is costly in the middle of a coding session. CodeCompanion acts before that happens, in two ways: * **Context editing** - Clears older tool results from the message history * **Compaction** - Summarises the conversation and replaces the message history with the summary Both apply to HTTP adapters only. ACP agents manage their own context. ### In the Chat Buffer CodeCompanion counts the tokens in the [chat buffer](/usage/chat-buffer/) after each response, and before tool output is sent back, then compares them with two [thresholds](/configuration/context-management): | Operation | Default trigger | |---|---| | Context editing | `0.65` of the context window | | Compaction | `0.85` of the context window | The lower threshold means the cheaper, lower risk editing runs first and more often, which delays the need for compaction. Once the conversation crosses the upper threshold, compaction runs instead. ### Context Editing > \[!NOTE] > Inspired by [Anthropic's context editing](https://platform.claude.com/docs/en/build-with-claude/context-editing) Context editing replaces the *content* of older tool results with a placeholder, leaving the conversation's shape intact. Tool calls and their results are never orphaned, and the token count drops: ``` <important>Tool result cleared to save context. Re-run the tool if you need this output</important> ``` Editing works in *cycles*. A cycle is one user turn and everything the LLM did in response, such as tool calls, tool results and replies. The last three cycles are kept in full, as set by `keep_cycles`, and older cycles have their tool results cleared. An in-flight [agent loop](/usage/chat-buffer/agents-tools#how-they-work) is never cut in half, as a cycle is kept or cleared as a whole. Output from the tools in `exclude_tools` is never cleared. By default that's the `memory` tool, as its output is often referenced later in the conversation. ### Compaction > \[!NOTE] > Inspired by [Claude Code's compaction prompt](https://github.com/Piebald-AI/claude-code-system-prompts) Compaction makes one LLM request to summarise the conversation so far, then replaces the message history with that summary. These are kept as they are: * The system prompt * [Rules](/usage/chat-buffer/rules) A summary from an earlier compaction isn't kept. The new summary replaces it. Files, buffers and images are replaced with a placeholder that names the source, so the LLM knows what to re-read or ask for: ``` <important>File content for `lua/codecompanion/init.lua` cleared during compaction. Re-read the file if you need it.</important> ``` Everything else is summarised and removed. Compaction is skipped if it would save fewer than 10,000 tokens, as set by `min_token_savings`. The summary is added to the chat as a new user message, and the chat is submitted so the LLM can carry on from where it left off. Compaction can use a cheaper or faster adapter than the chat itself. If that adapter fails, the round is skipped and the error is logged, unless you've set `fallback_to_chat_adapter`, in which case it retries with the chat's adapter. See [Configuring Context Management](/configuration/context-management) for both options. ### Server-Side Compaction For models that support it, the `anthropic` and `openai` adapters hand context management to the provider and CodeCompanion's own editing and compaction are turned off: | Adapter | Server-side features | |---|---| | `anthropic` | [Context editing](https://platform.claude.com/docs/en/build-with-claude/context-editing) and [compaction](https://platform.claude.com/docs/en/build-with-claude/compaction) | | `openai` | [Compaction](https://developers.openai.com/api/docs/guides/compaction) | Both use your thresholds, but the compaction trigger is never set below 50,000 tokens. To keep CodeCompanion in charge, see [Disabling Compaction](/configuration/adapters-http#disabling-compaction). ### Manual Triggers The [/compact](/usage/chat-buffer/slash-commands#compact) slash command compacts the conversation straight away, wherever the token count sits, and ignores `min_token_savings`. Editing has no manual equivalent. --- --- url: /agent-client-protocol.md description: >- Check which parts of the Agent Client Protocol (ACP) CodeCompanion implements, and how it talks to agents. --- # Agent Client Protocol (ACP) The [Agent Client Protocol (ACP)](https://agentclientprotocol.com/) is an open standard for connecting editors to coding agents. CodeCompanion implements it so you can work with agents like Claude Code and Codex from the chat buffer. This page lists what's supported and how it's implemented. To set up an agent, see [Configuring ACP Adapters](/configuration/adapters-acp). ## Implementation | Feature | Supported | Details | |---|---|---| | **Core Protocol** | ✅ | JSON-RPC 2.0, streaming responses, message buffering | | **Authentication** | ✅ | Multiple auth methods, adapter-level hooks | | **Content Types** | ✅ | Text and images | | **File System** | ✅ | Read and write text files, with line ranges | | **MCP Integration** | ✅ | Passes your [MCP servers](/configuration/adapters-acp#configuring-mcp-servers) to the agent | | **Permissions** | ✅ | Approve tool calls, with a diff preview for edits | | **Session Management** | ✅ | Create, list and load sessions | | **Session Config Options** | ✅ | Switch the agent's mode, model and other options | | **Tool Calls** | ✅ | Content blocks, file diffs, status updates | | **Agent Plans** | ❌ | Show an agent's execution plan | | **Terminal Operations** | ❌ | Give the agent a Neovim terminal | ### Client Capabilities CodeCompanion advertises these capabilities to agents: ```lua { fs = { readTextFile = true, writeTextFile = true, }, } ``` ### Content Types | Content Type | Send to Agent | Receive from Agent | |---|---|---| | Text | ✅ | ✅ | | File Diffs | N/A | ✅ | | Images | ✅ | ❌ | | Audio | ❌ | ❌ | | Embedded Resources | ❌ | ✅ | An embedded resource from an agent is shown as its text, or its URI if it has no text. Images and audio from an agent are shown as `[image]` and `[audio]`. ### State Management HTTP adapters are stateless and send the full conversation with every request. ACP agents are stateful: the agent holds the conversation, so CodeCompanion only sends new messages with each prompt and tracks the session ID throughout. ### Files and Buffers A file or buffer shared with an agent is sent as its path, not its content, and the agent reads it itself. This avoids the `<attachment>` tags that CodeCompanion uses for HTTP adapters. ### Slash Commands Agents can advertise their own slash commands. Type `\` in the chat buffer to complete them, and CodeCompanion turns `\command` into `/command` before sending your prompt. ### Session Config Options Agents expose their modes, models and other settings as [session config options](https://agentclientprotocol.com/protocol/session-config-options). CodeCompanion changes them with `session/set_config_option`. Change models with `ga` in the chat buffer, and anything else with the [/acp\_session\_options](/usage/chat-buffer/slash-commands#acp-session-options) slash command. ### Session Resume If an agent supports `session/list` and `session/load`, the [/resume](/usage/chat-buffer/slash-commands#resume) slash command lists its previous sessions and restores the one you pick into the chat buffer. ### Cleanup CodeCompanion disconnects from agents on Neovim's `VimLeavePre` autocmd, so their processes are stopped when Neovim exits. ## Protocol Version CodeCompanion implements **ACP protocol version 1**. The version is negotiated during initialisation. If an agent selects a different version, CodeCompanion logs a warning and carries on with the agent's version. ## Limitations * **System prompts** - ACP has no way to send a system prompt. CodeCompanion doesn't merge its own into your messages either, so an agent behaves as it would if you used it directly * **Terminal operations** - The `terminal/*` methods aren't implemented, and CodeCompanion doesn't advertise a terminal capability * **Agent plans** - [Plan](https://agentclientprotocol.com/protocol/agent-plan) updates from agents aren't shown in the chat buffer * **Audio** - Audio can't be sent or received ## See Also * [Agent Client Protocol Specification](https://agentclientprotocol.com/) - The official ACP documentation * [Configuring ACP Adapters](/configuration/adapters-acp) - Setup instructions for each agent * [Using Agents and Tools](/usage/chat-buffer/agents-tools) - Working with agents in the chat buffer --- --- url: /model-context-protocol.md description: >- Check which parts of the Model Context Protocol (MCP) CodeCompanion implements, and how MCP servers reach the chat buffer. --- # Model Context Protocol (MCP) The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) is an open standard for connecting LLMs to external systems. CodeCompanion implements the parts of it that help you code: running servers and using their tools and prompts. ## Usage [Default servers](/configuration/mcp#default-servers) start when you open your first chat buffer, and their tools are added to it. Start or stop any server with the [/mcp](/usage/chat-buffer/slash-commands#mcp) slash command. Once a server is running, its [tools](/usage/chat-buffer/agents-tools#mcp) are available in the chat buffer, and its prompts can be added with the [/mcp-prompts](/usage/chat-buffer/slash-commands#mcp-prompts) slash command. ACP agents don't use CodeCompanion's MCP client. Instead, your servers can be [passed to the agent](/configuration/adapters-acp#configuring-mcp-servers). ## Implementation | Feature | Supported | Details | |---|---|---| | Transport: Stdio | ✅ | | | Transport: Streamable HTTP | ❌ | | | Basic: Cancellation | ✅ | On timeout, or when you cancel | | Basic: Progress | ❌ | | | Basic: Task | ❌ | | | Client: Roots | ✅ | Disabled by default | | Client: Sampling | ❌ | | | Client: Elicitation | ❌ | | | Server: Completion | ❌ | | | Server: Pagination | ✅ | | | Server: Prompts | ✅ | Text content from user messages only | | Server: Resources | ❌ | | | Server: Tools | ✅ | Text content only | | Server: Tool list changed notification | ❌ | | ## Protocol Version CodeCompanion implements MCP version **2025-11-25**. ## See Also * [Model Context Protocol Specification](https://modelcontextprotocol.io/specification/2025-11-25) - The official MCP documentation * [Configuring MCP Servers](/configuration/mcp) - Adding servers and overriding their tools --- --- url: /integrations.md description: >- Connect CodeCompanion to other applications, including herdr and Rex, which show whether CodeCompanion is idle, working or blocked. --- # Integrations CodeCompanion fires [events](/usage/events) at various points during the lifecycle, so other applications can follow what it's doing. Some integrations are built in. ## herdr [herdr](https://github.com/herdrdev/herdr) supervises terminal panes and shows which agent is running in each one. When Neovim runs in a herdr pane, or a terminal in the Herdr app, CodeCompanion [reports its state](https://herdr.dev/docs/integrations/#integrate-your-own-agent) to herdr, so the pane sits alongside panes running Claude Code or Codex: | State | Description | |---|---| | `working` | A chat buffer or CLI interaction is waiting on a response, compacting or running tools | | `blocked` | A tool approval or a question from the LLM is waiting on you | | `done` | A turn finished whilst you were looking elsewhere (Herdr app only) | | `idle` | Nothing is in progress | The status of a chat buffer or CLI interaction from within Neovim is shown as the pane's state. A `blocked` interaction takes priority and the Herdr app shows whether that pane is waiting on a tool approval or a question. In the Herdr app, panes are given the active chat's title, or `CodeCompanion [branch]`. This is skipped when Neovim's `'title'` option is on, as Neovim then sets the terminal title itself. The integration is enabled by default, and does nothing outside of herdr. To disable it: ```lua require("codecompanion").setup({ integrations = { herdr = { enabled = false, }, }, }) ``` The Herdr app shows an icon for each agent from its programs table. To give CodeCompanion the Neovim icon, add this to `~/.config/herdr-app/programs.json`: ```json { "programs": { "codecompanion": { "name": "CodeCompanion", "icon": "icons/nvim.svg", "tint": true, "tile": "#57a143", "color": "#ffffff" } } } ``` **Limitations** * In the Herdr app, the state doesn't reach herdr when Neovim runs inside tmux or another multiplexer ## Rex [Rex](https://www.superlogical.com/rex) is a terminal multiplexer that shows what the programs in its panes are doing. When Neovim runs in Rex, CodeCompanion reports each chat buffer and CLI interaction with the [Program Status Protocol](https://www.superlogical.com/rex/docs/build/program-status), so they appear in Rex's tabs, session picker and activity view: | State | Description | |---|---| | `working` | Waiting on a response, compacting or running tools | | `blocked` | A tool approval or a question from the LLM is waiting on you | | `done` | Finished a response you haven't looked at yet | | `idle` | Open, with nothing in progress | Each chat buffer and CLI interaction is reported separately, under its title where it has one. Closing it removes it from Rex, as does quitting Neovim. The integration is enabled by default, and does nothing outside of Rex. To disable it: ```lua require("codecompanion").setup({ integrations = { rex = { enabled = false, }, }, }) ``` --- --- url: /configuration/upgrading.md description: >- Update your config for the breaking changes in each major version of CodeCompanion. --- # Upgrading CodeCompanion CodeCompanion follows [semantic versioning](https://semver.org/), so breaking changes only arrive in a major version. Each section below lists what changed and how to update your config. To upgrade when you're ready rather than when your plugin manager decides, pin CodeCompanion to a version. See the [installation guide](/installation). ## v20.0.1 to v21.0.0 ### Chat Tool output is no longer written to the chat buffer. Each tool call is a single line, and pressing `K` on it shows the call's arguments and output. See [How They Work](/usage/chat-buffer/agents-tools#how-they-work). These have been removed: * The `display` key in `add_tool_output`. Only `content` is used * `data.display` in the [on\_tool\_output](/configuration/callbacks#truncating-tool-output) callback * `interactions.chat.tools.opts.folds`, as there's no tool output left to fold * The `CodeCompanionChatToolText`, `CodeCompanionChatToolSuccess` and `CodeCompanionChatToolFailure` highlight groups In a tool's output handler: ```lua chat:add_tool_output(self, { content = "The result is 42", display = "Calculated 6 * 7" }) -- [!code --] chat:add_tool_output(self, { content = "The result is 42" }) -- [!code ++] ``` ## v19.27.0 to v20.0.0 > \[!IMPORTANT] > The `openai` and `gemini` adapters now use different APIs. If you use either of them, read the [Adapters](#adapters) section before upgrading ### Adapters * `openai` now uses OpenAI's [Responses API](https://platform.openai.com/docs/api-reference/responses) and `gemini` uses Google's [Interactions API](https://ai.google.dev/gemini-api/docs/interactions). The previous adapters are now `openai_legacy` and `gemini_legacy` * Adapters built with `extend("openai")` or `extend("gemini")` use the new APIs too * `openai_responses` and `gemini_interactions` are removed. Use `openai` and `gemini` instead * In your own plugins, change `require("codecompanion.adapters.http.openai")` to `require("codecompanion.adapters.http.openai_legacy")` * The GitHub Models adapter is removed To keep using the previous APIs: ::: code-group ```lua [Interactions] require("codecompanion").setup({ interactions = { chat = { adapter = "openai_legacy", -- Can be "openai_legacy" or "gemini_legacy" }, inline = { adapter = "openai_legacy", }, }, }) ``` ```lua [Extending] require("codecompanion").setup({ adapters = { http = { openai = function() return require("codecompanion.adapters").extend("openai_legacy", { -- [!code ++] env = { api_key = "OPENAI_API_KEY", }, }) end, }, }, }) ``` ::: * HTTP adapter handlers in the nested format (`lifecycle`, `request`, `response` and `tools`) now take a single `args` table after `self`, in place of positional arguments. New fields can be added without breaking your handlers, and a handler that wraps another can pass `args` straight through. If you've written or extended an adapter in this format, update each handler: ```lua require("codecompanion").setup({ adapters = { http = { openai = function() return require("codecompanion.adapters").extend("openai", { handlers = { response = { parse_chat = function(self, data, tools) -- [!code --] parse_chat = function(self, args) -- [!code ++] local data, tools = args.data, args.tools -- [!code ++] -- ... end, }, }, }) end, }, }, }) ``` The fields in `args` match the old positional arguments: | Handler | `args` | | --- | --- | | `lifecycle.on_exit` | `data` | | `request.build_parameters` | `params`, `messages` | | `request.build_messages` | `messages` | | `request.build_tools` | `tools` | | `request.build_structured_output` | `schema` | | `request.build_reasoning` | `data` | | `request.build_body` | `payload` | | `response.parse_chat` | `data`, `tools` | | `response.parse_tokens` | `data` | | `response.parse_meta` | `data` | | `tools.format_calls` | `tools` | | `tools.format_response` | `tool_call`, `output` | `lifecycle.setup` and `lifecycle.teardown` still take only `self`. Handlers in the flat format, such as `form_messages` and `chat_output`, are unaffected. * ACP adapters now use the same nested format, with `lifecycle.setup`, `lifecycle.auth`, `lifecycle.on_exit` and `request.build_messages`. Flat handlers still work, and take precedence over nested ones when you extend an adapter. `helpers.form_messages` is now `helpers.build_messages`, though the old name still works: ```lua require("codecompanion").setup({ adapters = { acp = { claude_code = function() return require("codecompanion.adapters").extend("claude_code", { handlers = { form_messages = function(self, messages, capabilities) -- [!code --] request = { -- [!code ++] build_messages = function(self, args) -- [!code ++] -- args.messages, args.capabilities end, }, -- [!code ++] }, }) end, }, }, }) ``` ### Slash Commands * The `/image` slash command is removed. Select an image with [/file](/usage/chat-buffer/slash-commands#file), or use the new [/file-from-url](/usage/chat-buffer/slash-commands#file-from-url) slash command for an image at a URL. Move any `opts.dirs` you set for `/image` to `/file`, and delete `opts.filetypes` and `opts.provider`, which have no equivalent: ```lua require("codecompanion").setup({ interactions = { chat = { slash_commands = { ["image"] = { -- [!code --] ["file"] = { -- [!code ++] opts = { dirs = { "~/Pictures" }, }, }, }, }, }, }) ``` ### Tools * The `insert_edit_into_file` tool is replaced by [edit\_file](/usage/chat-buffer/agents-tools#edit-file) ([#3427](https://github.com/olimorris/codecompanion.nvim/pull/3427)). It takes the same options, so rename it in your config, custom groups and prompts: ```lua require("codecompanion").setup({ interactions = { chat = { tools = { ["insert_edit_into_file"] = { -- [!code --] ["edit_file"] = { -- [!code ++] opts = { require_confirmation_after = false, }, }, }, }, }, }) ``` * The [web\_search](/usage/chat-buffer/agents-tools#web-search) tool now uses DuckDuckGo by default, which doesn't need an API key. Previously it used Tavily * The [fetch\_webpage](/usage/chat-buffer/agents-tools#fetch-webpage) tool and [/fetch](/usage/chat-buffer/slash-commands#fetch) slash command now use [MarkItDown](https://github.com/microsoft/markitdown) by default. Previously they used Jina > \[!IMPORTANT] > MarkItDown runs the `markitdown` CLI on your machine, so it must be installed. `:checkhealth codecompanion` reports if it's missing To keep the previous defaults: ```lua require("codecompanion").setup({ interactions = { chat = { slash_commands = { ["fetch"] = { opts = { adapter = "jina", }, }, }, tools = { ["fetch_webpage"] = { opts = { adapter = "jina", }, }, ["web_search"] = { opts = { adapter = "tavily", }, }, }, }, }, }) ``` * `interactions.chat.tools.opts.auto_submit_errors` and `interactions.chat.tools.opts.auto_submit_success` are removed. A tool's output is always sent back to the LLM, as part of the [agent loop](/usage/chat-buffer/agents-tools#how-they-work) ### Chat Chat buffer methods that took several positional arguments now take a table. Update any calls from a custom tool, slash command or callback: | Method | Before | After | | --- | --- | --- | | `add_tool_output` | `(tool, for_llm, for_user)` | `(tool, { content, display? })` | | `add_context` | `(data, source, id, opts)` | `(data, { source, id, bufnr?, path?, tag?, visible?, context_opts? })` | | `change_adapter` | `(adapter, callback)` | `({ adapter, model?, callback? })` | | `done` | `(output, reasoning, tools, meta, opts)` | `({ output?, reasoning?, tools?, meta?, status?, error? })` | | `update_buf_line` | `(line_number, content, opts)` | `({ line_number, content, status?, icon_id?, priority?, virt_text_pos? })` | For example, in a tool's output handler: ```lua chat:add_tool_output(self, "The result is 42", "Calculated 6 * 7") -- [!code --] chat:add_tool_output(self, { content = "The result is 42", display = "Calculated 6 * 7" }) -- [!code ++] ``` ### Inline The inline interaction now edits the buffer with the [edit\_file](/usage/chat-buffer/agents-tools#edit-file) tool, rather than asking the LLM where to place a block of code. See [How Edits Work](/usage/inline#how-edits-work). > \[!IMPORTANT] > The default inline adapter is now `copilot` with the `gpt-5.4-mini` model, as editing through a tool needs a capable model to be reliable. This makes inline unusable on Copilot's free and student plans, so [change the adapter](/configuration/inline#changing-adapter) if you're on one **Update your config** If you've customised inline: * Delete the `#{buffer}` editor context from your inline prompts. The current buffer is always shared with the LLM, up to a [context limit](/configuration/inline#context-limit) * Delete `placement` from your inline prompts. Writing code into a new buffer is no longer an option * Delete `display.inline.layout` * Delete the `response.parse_inline` and `inline_output` handlers from your adapters. Inline now uses `response.parse_chat` (or `chat_output`) and the tool handlers, the same as the chat buffer * `pre_hook` no longer runs for inline prompts * The `Unit tests` prompt (`/tests`) has been removed from the prompt library **What's changed** * The adapter's model must support tool calling. Inline prompts sent with an adapter that doesn't, such as `xai`, are refused with an error * With a visual selection, the LLM can only edit the selected lines * A reply that doesn't edit the buffer, such as an answer to a question, opens in a float rather than a chat buffer * `:CodeCompanion` with no prompt, and prompts with `user_prompt`, now open CodeCompanion's input box rather than `vim.ui.input` * The inline diff can be reviewed a hunk at a time with the new `accept_hunk` (`ga`), `reject_hunk` (`gr`) and `undo_hunk` (`u`) keymaps in `interactions.shared.keymaps`. `reject_change` now rejects only the hunks that are left > \[!IMPORTANT] > Your `default` rules group, including `AGENTS.md` and `CLAUDE.md`, is sent with every inline prompt to an HTTP adapter, on top of the context limit. Set `rules.opts.inline.autoload = {}` to stop it. See [Rules and Skills](/configuration/inline#rules-and-skills) ## v18.7.0 to v19.0.0 * The Super Diff is removed ([#2600](https://github.com/olimorris/codecompanion.nvim/pull/2600)) * CodeCompanion only supports its built-in diff, which is enabled by default. Mini.Diff is no longer supported ([#2600](https://github.com/olimorris/codecompanion.nvim/pull/2600)) * The `full_stack_dev` group is renamed to [agent](/usage/chat-buffer/agents-tools#agent) ([#2786](https://github.com/olimorris/codecompanion.nvim/pull/2786)) * The `next_edit_suggestion` and `list_code_usages` tools are removed ### Adapters * The Claude Code adapter needs Zed's [claude-agent-acp](https://github.com/zed-industries/claude-agent-acp) installed, which was previously named *claude-code-acp* ([#2779](https://github.com/olimorris/codecompanion.nvim/pull/2779)) ### Config * Diff keymaps have moved from `interactions.inline.keymaps` to `interactions.shared.keymaps` ([#2600](https://github.com/olimorris/codecompanion.nvim/pull/2600)) * All diff config has moved to `display.diff` ([#2600](https://github.com/olimorris/codecompanion.nvim/pull/2600)) * `variables` are renamed to `editor_context`, at `interactions.chat.editor_context` and `interactions.inline.editor_context` ([#2719](https://github.com/olimorris/codecompanion.nvim/pull/2719)) * Across editor context, slash commands and tools, string values for `callback` (module paths and file paths) move to `path`. `callback` is still used for functions ### Prompt Library * Rules in a prompt library item move from `opts.rules` to `rules`: ```markdown --- name: Oli's test workflow strategy: chat description: Workflow test prompt rules: - test_rule --- ``` ## v17.33.0 to v18.0.0 ### Config * `strategies` is renamed to `interactions`. This only breaks your config if it references `codecompanion.strategies`, which becomes `codecompanion.interactions` ([#2485](https://github.com/olimorris/codecompanion.nvim/pull/2485)) * The `/catalog` folders for built-in slash commands and tools are renamed to `/builtin`. Update any paths in your config that point at them ([#2482](https://github.com/olimorris/codecompanion.nvim/pull/2482)) * Workspaces are removed. Use [rules](/configuration/rules) instead ### Adapters * In a custom adapter, rename `condition` to `enabled` on any schema items ([#2439](https://github.com/olimorris/codecompanion.nvim/pull/2439/commits/cb14c7bac869346e2d12b775c4bf258606add569)): ```lua return { schema = { ["reasoning.effort"] = { ---@type fun(self: CodeCompanion.HTTPAdapter): boolean condition = function(self) -- [!code --] enabled = function(self) -- [!code ++] -- end, }, } } ``` * The default models for the `anthropic` and `gemini` adapters are now `claude-sonnet-4-5-20250929` and `gemini-3-pro-preview` ([#2494](https://github.com/olimorris/codecompanion.nvim/pull/2494)) * To hide the adapters that come with CodeCompanion, `adapters.[acp|http].opts.show_defaults` is renamed to `adapters.[acp|http].opts.show_presets` ([#2497](https://github.com/olimorris/codecompanion.nvim/pull/2497)) ### Chat * Memory is renamed to [rules](/configuration/rules). Rename any `memory` references in your config to `rules` ([#2440](https://github.com/olimorris/codecompanion.nvim/pull/2440)) * `default_memory` is renamed to `autoload` ([#2509](https://github.com/olimorris/codecompanion.nvim/pull/2509)) * `#{buffer}{watch}` is renamed to `#{buffer}{diff}`, as the LLM receives a diff of the buffer's changes with each request ([#2444](https://github.com/olimorris/codecompanion.nvim/pull/2444)) * `#{buffer}{pin}` is renamed to `#{buffer}{all}`, as the entire buffer is sent with each request ([#2444](https://github.com/olimorris/codecompanion.nvim/pull/2444)) * To pass an adapter to `:CodeCompanionChat`, use `:CodeCompanionChat adapter=<adapter_name>` ([#2437](https://github.com/olimorris/codecompanion.nvim/pull/2437)) * A chat system prompt at `opts.system_prompt` moves to `interactions.chat.opts.system_prompt` ([#2484](https://github.com/olimorris/codecompanion.nvim/pull/2484)) ### Prompt Library If you define prompts in your config: * Rename `opts.short_name` to `opts.alias` on each item, so you can still call it with `require("codecompanion").prompt("my_prompt")` or as a slash command in the chat buffer ([#2471](https://github.com/olimorris/codecompanion.nvim/pull/2471)): ```lua ["my custom prompt"] = { strategy = "chat", description = "My custom prompt", opts = { short_name = "my_prompt", -- [!code --] alias = "my_prompt", -- [!code ++] }, prompts = { -- ... }, }, ``` * In workflow prompts, replace `strategy = "workflow"` with `interaction = "chat"` and set `opts.is_workflow = true` ([#2487](https://github.com/olimorris/codecompanion.nvim/pull/2487)): ```lua ["my_workflow"] = { strategy = "workflow", -- [!code --] interaction = "chat", -- [!code ++] description = "My custom workflow", opts = { is_workflow = true, -- [!code ++] }, prompts = { -- ... }, }, ``` * To hide the built-in prompt library items, `display.action_palette.show_default_prompt_library` is renamed to `display.action_palette.show_preset_prompts` ([#2499](https://github.com/olimorris/codecompanion.nvim/pull/2499)) ### Tools If you configure tools, rename: * `requires_approval` to `require_approval_before` ([#2439](https://github.com/olimorris/codecompanion.nvim/pull/2439/commits/cb14c7bac869346e2d12b775c4bf258606add569)) * `user_confirmation` to `require_confirmation_after` ([#2450](https://github.com/olimorris/codecompanion.nvim/pull/2450)) ### UI * `display.chat.child_window` is renamed to `display.chat.floating_window` ([#2452](https://github.com/olimorris/codecompanion.nvim/pull/2452)) * `display.action_palette.opts.show_default_actions` is renamed to `display.action_palette.opts.show_preset_actions` ([#2499](https://github.com/olimorris/codecompanion.nvim/pull/2499)) --- --- url: /troubleshooting.md description: >- Diagnose problems with CodeCompanion in Neovim with the health check, the log file and the debug window. --- # Troubleshooting When something doesn't work, start with the health check, then the log file. To see exactly what's sent to the LLM, use the debug window. ## Health Check To check CodeCompanion's dependencies, Tree-sitter parsers and libraries: ``` :checkhealth codecompanion ``` It also prints the path to the log file. ## Log File Errors are written to `codecompanion.log` in Neovim's log directory, `stdpath("log")`. For more detail, raise the log level: ```lua require("codecompanion").setup({ opts = { log_level = "DEBUG", -- Can be "TRACE", "DEBUG", "INFO" or "ERROR" }, }) ``` At `DEBUG`, every request is logged, along with the paths of the files holding its body and response. Set the level back to `ERROR` once you're done, as the log grows quickly. ## Debug Window Press `gd` in a chat buffer to open the [debug window](/usage/chat-buffer/#debug-window). It shows the adapter's settings and the full message history that's sent on the next turn, including the system prompt and anything hidden from the chat buffer. ## Minimal Config To rule out your own config and other plugins, reproduce the problem with [minimal.lua](https://github.com/olimorris/codecompanion.nvim/blob/main/minimal.lua): ```sh nvim --clean -u minimal.lua ``` Include your `minimal.lua` and the relevant lines from the log when you [open an issue](https://github.com/olimorris/codecompanion.nvim/issues).