Upgrading CodeCompanion
CodeCompanion follows semantic versioning, 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.
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 section before upgrading
Adapters
openainow uses OpenAI's Responses API andgeminiuses Google's Interactions API. The previous adapters are nowopenai_legacyandgemini_legacy- Adapters built with
extend("openai")orextend("gemini")use the new APIs too openai_responsesandgemini_interactionsare removed. Useopenaiandgeminiinstead- In your own plugins, change
require("codecompanion.adapters.http.openai")torequire("codecompanion.adapters.http.openai_legacy") - The GitHub Models adapter is removed
To keep using the previous APIs:
require("codecompanion").setup({
interactions = {
chat = {
adapter = "openai_legacy", -- Can be "openai_legacy" or "gemini_legacy"
},
inline = {
adapter = "openai_legacy",
},
},
})require("codecompanion").setup({
adapters = {
http = {
openai = function()
return require("codecompanion.adapters").extend("openai_legacy", {
env = {
api_key = "OPENAI_API_KEY",
},
})
end,
},
},
})- HTTP adapter handlers in the nested format (
lifecycle,request,responseandtools) now take a singleargstable afterself, in place of positional arguments. New fields can be added without breaking your handlers, and a handler that wraps another can passargsstraight through. If you've written or extended an adapter in this format, update each handler:
require("codecompanion").setup({
adapters = {
http = {
openai = function()
return require("codecompanion.adapters").extend("openai", {
handlers = {
response = {
parse_chat = function(self, data, tools)
parse_chat = function(self, args)
local data, tools = args.data, args.tools
-- ...
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_exitandrequest.build_messages. Flat handlers still work, and take precedence over nested ones when you extend an adapter.helpers.form_messagesis nowhelpers.build_messages, though the old name still works:
require("codecompanion").setup({
adapters = {
acp = {
claude_code = function()
return require("codecompanion.adapters").extend("claude_code", {
handlers = {
form_messages = function(self, messages, capabilities)
request = {
build_messages = function(self, args)
-- args.messages, args.capabilities
end,
},
},
})
end,
},
},
})Slash Commands
- The
/imageslash command is removed. Select an image with /file, or use the new /file-from-url slash command for an image at a URL. Move anyopts.dirsyou set for/imageto/file, and deleteopts.filetypesandopts.provider, which have no equivalent:
require("codecompanion").setup({
interactions = {
chat = {
slash_commands = {
["image"] = {
["file"] = {
opts = {
dirs = { "~/Pictures" },
},
},
},
},
},
})Tools
- The
insert_edit_into_filetool is replaced by edit_file (#3427). It takes the same options, so rename it in your config, custom groups and prompts:
require("codecompanion").setup({
interactions = {
chat = {
tools = {
["insert_edit_into_file"] = {
["edit_file"] = {
opts = {
require_confirmation_after = false,
},
},
},
},
},
})- The web_search tool now uses DuckDuckGo by default, which doesn't need an API key. Previously it used Tavily
- The fetch_webpage tool and /fetch slash command now use 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:
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_errorsandinteractions.chat.tools.opts.auto_submit_successare removed. A tool's output is always sent back to the LLM, as part of the agent loop
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:
chat:add_tool_output(self, "The result is 42", "Calculated 6 * 7")
chat:add_tool_output(self, { content = "The result is 42", display = "Calculated 6 * 7" }) Inline
The inline interaction now edits the buffer with the edit_file tool, rather than asking the LLM where to place a block of code. See 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 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 - Delete
placementfrom your inline prompts. Writing code into a new buffer is no longer an option - Delete
display.inline.layout - Delete the
response.parse_inlineandinline_outputhandlers from your adapters. Inline now usesresponse.parse_chat(orchat_output) and the tool handlers, the same as the chat buffer pre_hookno longer runs for inline prompts- The
Unit testsprompt (/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
:CodeCompanionwith no prompt, and prompts withuser_prompt, now open CodeCompanion's input box rather thanvim.ui.input- The inline diff can be reviewed a hunk at a time with the new
accept_hunk(ga),reject_hunk(gr) andundo_hunk(u) keymaps ininteractions.shared.keymaps.reject_changenow 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
v18.7.0 to v19.0.0
- The Super Diff is removed (#2600)
- CodeCompanion only supports its built-in diff, which is enabled by default. Mini.Diff is no longer supported (#2600)
- The
full_stack_devgroup is renamed to agent (#2786) - The
next_edit_suggestionandlist_code_usagestools are removed
Adapters
- The Claude Code adapter needs Zed's claude-agent-acp installed, which was previously named claude-code-acp (#2779)
Config
- Diff keymaps have moved from
interactions.inline.keymapstointeractions.shared.keymaps(#2600) - All diff config has moved to
display.diff(#2600) variablesare renamed toeditor_context, atinteractions.chat.editor_contextandinteractions.inline.editor_context(#2719)- Across editor context, slash commands and tools, string values for
callback(module paths and file paths) move topath.callbackis still used for functions
Prompt Library
- Rules in a prompt library item move from
opts.rulestorules:
---
name: Oli's test workflow
strategy: chat
description: Workflow test prompt
rules:
- test_rule
---v17.33.0 to v18.0.0
Config
strategiesis renamed tointeractions. This only breaks your config if it referencescodecompanion.strategies, which becomescodecompanion.interactions(#2485)- The
/catalogfolders for built-in slash commands and tools are renamed to/builtin. Update any paths in your config that point at them (#2482) - Workspaces are removed. Use rules instead
Adapters
- In a custom adapter, rename
conditiontoenabledon any schema items (#2439):
return {
schema = {
["reasoning.effort"] = {
---@type fun(self: CodeCompanion.HTTPAdapter): boolean
condition = function(self)
enabled = function(self)
--
end,
},
}
}- The default models for the
anthropicandgeminiadapters are nowclaude-sonnet-4-5-20250929andgemini-3-pro-preview(#2494) - To hide the adapters that come with CodeCompanion,
adapters.[acp|http].opts.show_defaultsis renamed toadapters.[acp|http].opts.show_presets(#2497)
Chat
- Memory is renamed to rules. Rename any
memoryreferences in your config torules(#2440) default_memoryis renamed toautoload(#2509)#{buffer}{watch}is renamed to#{buffer}{diff}, as the LLM receives a diff of the buffer's changes with each request (#2444)#{buffer}{pin}is renamed to#{buffer}{all}, as the entire buffer is sent with each request (#2444)- To pass an adapter to
:CodeCompanionChat, use:CodeCompanionChat adapter=<adapter_name>(#2437) - A chat system prompt at
opts.system_promptmoves tointeractions.chat.opts.system_prompt(#2484)
Prompt Library
If you define prompts in your config:
- Rename
opts.short_nametoopts.aliason each item, so you can still call it withrequire("codecompanion").prompt("my_prompt")or as a slash command in the chat buffer (#2471):
["my custom prompt"] = {
strategy = "chat",
description = "My custom prompt",
opts = {
short_name = "my_prompt",
alias = "my_prompt",
},
prompts = {
-- ...
},
},- In workflow prompts, replace
strategy = "workflow"withinteraction = "chat"and setopts.is_workflow = true(#2487):
["my_workflow"] = {
strategy = "workflow",
interaction = "chat",
description = "My custom workflow",
opts = {
is_workflow = true,
},
prompts = {
-- ...
},
},- To hide the built-in prompt library items,
display.action_palette.show_default_prompt_libraryis renamed todisplay.action_palette.show_preset_prompts(#2499)
Tools
If you configure tools, rename:
requires_approvaltorequire_approval_before(#2439)user_confirmationtorequire_confirmation_after(#2450)