A history management extension for codecompanion AI chat plugin that enables saving, browsing and restoring chat sessions.
A history management extension for codecompanion.nvim that enables saving, browsing and restoring chat sessions.
The following CodeCompanion features are preserved when saving and restoring chats:
Feature | Status | Notes |
---|---|---|
System Prompts | ✅ | System prompt used in the chat |
Messages History | ✅ | All messages |
Images | ✅ | Restores images as base64 strings |
LLM Adapter | ✅ | The specific adapter used for the chat |
LLM Settings | ✅ | Model, temperature and other adapter settings |
Tools | ✅ | Tool schemas and their system prompts |
Tool Outputs | ✅ | Tool execution results |
Variables | ✅ | Variables used in the chat |
References | ✅ | Code snippets and command outputs added via slash commands |
Pinned References | ✅ | Pinned references |
Watchers | ⚠ | Saved but requires original buffer context to resume watching |
When restoring a chat:
Note: While watched buffer states are saved, they require the original buffer context to resume watching functionality.
[!NOTE] As this is an extension that deeply integrates with CodeCompanion's internal APIs, occasional compatibility issues may arise when CodeCompanion updates. If you encounter any bugs or unexpected behavior, please raise an issue to help us maintain compatibility.
Using lazy.nvim:
{
"olimorris/codecompanion.nvim",
dependencies = {
--other plugins
"ravitemer/codecompanion-history.nvim"
}
}
require("codecompanion").setup({
extensions = {
history = {
enabled = true,
opts = {
-- Keymap to open history from chat buffer (default: gh)
keymap = "gh",
-- Keymap to save the current chat manually (when auto_save is disabled)
save_chat_keymap = "sc",
-- Save all chats by default (disable to save only manually using 'sc')
auto_save = true,
-- Number of days after which chats are automatically deleted (0 to disable)
expiration_days = 0,
-- Picker interface (auto resolved to a valid picker)
picker = "telescope", --- ("telescope", "snacks", "fzf-lua", or "default")
-- Customize picker keymaps (optional)
picker_keymaps = {
rename = { n = "r", i = "<M-r>" },
delete = { n = "d", i = "<M-d>" },
duplicate = { n = "<C-y>", i = "<C-y>" },
},
---Automatically generate titles for new chats
auto_generate_title = true,
title_generation_opts = {
---Adapter for generating titles (defaults to current chat adapter)
adapter = nil, -- "copilot"
---Model for generating titles (defaults to current chat model)
model = nil, -- "gpt-4o"
---Number of user prompts after which to refresh the title (0 to disable)
refresh_every_n_prompts = 0, -- e.g., 3 to refresh after every 3rd user prompt
---Maximum number of times to refresh the title (default: 3)
max_refreshes = 3,
},
---On exiting and entering neovim, loads the last chat on opening chat
continue_last_chat = false,
---When chat is cleared with `gx` delete the chat from history
delete_on_clearing_chat = false,
---Directory path to save the chats
dir_to_save = vim.fn.stdpath("data") .. "/codecompanion-history",
---Enable detailed logging for history extension
enable_logging = false,
---Optional filter function to control which chats are shown when browsing
chat_filter = nil, -- function(chat_data) return boolean end
}
}
}
})
:CodeCompanionHistory
- Open the history browsergh
- Open history browser (customizable via opts.keymap
)sc
- Save current chat manually (customizable via opts.save_chat_keymap
)The history browser shows all your saved chats with:
Actions in history browser:
<CR>
- Open selected chatd
- Delete selected chat(s)r
- Rename selected chat<C-y>
- Duplicate selected chat<M-d>
(Alt+d) - Delete selected chat(s)<M-r>
(Alt+r) - Rename selected chat<C-y>
- Duplicate selected chatNote: Delete, rename, and duplicate actions are only available in telescope, snacks, and fzf-lua pickers. Multiple chats can be selected for deletion using picker's multi-select feature (press
<Tab>
). Duplication is limited to one chat at a time.
The extension can automatically refresh chat titles as conversations evolve:
refresh_every_n_prompts
: Set to refresh the title after every N user prompts (e.g., 3 means refresh after the 3rd, 6th, 9th user message)max_refreshes
: Limits how many times a title can be refreshed to avoid excessive API calls[truncated]
indicator[conversation truncated]
indicatorExample configuration for title refresh:
title_generation_opts = {
refresh_every_n_prompts = 3, -- Refresh after every 3rd user prompt
max_refreshes = 10, -- Allow up to 10 refreshes per chat
}
The extension supports flexible chat filtering to help you focus on relevant conversations:
Configurable Filtering:
chat_filter = function(chat_data)
return chat_data.cwd == vim.fn.getcwd()
end
-- Recent chats only (last 7 days)
chat_filter = function(chat_data)
local seven_days_ago = os.time() - (7 * 24 * 60 * 60)
return chat_data.updated_at >= seven_days_ago
end
Chat Index Data Structure: Each chat index entry (used in filtering) includes the following information:
-- ChatIndexData - lightweight metadata used for browsing and filtering
{
save_id = "1672531200", -- Unique chat identifier
title = "Debug API endpoint", -- Chat title (auto-generated or custom)
cwd = "/home/user/my-project", -- Working directory when saved
project_root = "/home/user/my-project", -- Detected project root
adapter = "openai", -- LLM adapter used
model = "gpt-4", -- Model name
updated_at = 1672531200, -- Unix timestamp of last update
message_count = 15, -- Number of messages in chat
token_estimate = 3420, -- Estimated token count
}
The history extension exports the following functions that can be accessed via require("codecompanion").extensions.history
:
-- Get the storage location for saved chats
get_location(): string?
-- Save a chat to storage (uses last chat if none provided)
save_chat(chat?: CodeCompanion.Chat)
-- Browse chats with custom filter function
browse_chats(filter_fn?: function(ChatIndexData): boolean)
-- Get metadata for all saved chats with optional filtering
get_chats(filter_fn?: function(ChatIndexData): boolean): table<string, ChatIndexData>
-- Load a specific chat by its save_id
load_chat(save_id: string): ChatData?
-- Delete a chat by its save_id
delete_chat(save_id: string): boolean
-- Duplicate a chat by its save_id
duplicate_chat(save_id: string, new_title?: string): string?
Example usage:
local history = require("codecompanion").extensions.history
-- Browse chats with project filter
history.browse_chats(function(chat_data)
return chat_data.project_root == utils.find_project_root()
end)
-- Get all saved chats metadata
local chats = history.get_chats()
-- Load a specific chat
local chat_data = history.load_chat("some_save_id")
-- Delete a chat
history.delete_chat("some_save_id")
-- Duplicate a chat with custom title
local new_save_id = history.duplicate_chat("some_save_id", "My Custom Copy")
-- Duplicate a chat with auto-generated title (appends "(1)")
local new_save_id = history.duplicate_chat("some_save_id")
graph TD
subgraph CodeCompanion Core Lifecycle
A[CodeCompanionChatCreated Event] --> B{Chat Submitted};
B --> C[LLM Response Received];
subgraph Chat End
direction RL
D[CodeCompanionChatCleared Event];
end
C --> D;
B --> D;
end
subgraph Extension Integration
A -- Extension Hooks --> E[Init & Subscribe];
E --> F[Setup Auto-Save];
F --> G[Prepare Auto-Title];
C -- Extension Hooks --> H[Subscriber Triggered];
H --> H1{Auto-Save Enabled?};
H1 -- Yes --> I[Save Chat State - Messages, Tools, Refs];
H1 -- No --> H2[Manual Save via `sc`];
H2 --> I;
I --> J{No Title & Auto-Title Enabled?};
J -- Yes --> K[Generate Title];
K --> L[Update Buffer Title];
L --> M[Save Chat with New Title];
J -- No --> B;
M --> B;
D -- Extension Hooks --> N[Respond to Clear Event];
N --> O[Delete Chat from Storage];
O --> P[Reset Extension State - Title/ID];
end
subgraph User History Interaction
Q[User Action - gh / :CodeCompanionHistory] --> R{History Browser};
R -- Restore --> S[Load Chat State from Storage];
S --> A;
R -- Delete --> O;
end
Here's what's happening in simple terms:
When you create a new chat, our extension jumps in and sets up two things:
As you chat:
sc
keymapWhen you clear a chat:
Any time you want to look at old chats:
gh
or the command to open the history browserThe extension integrates with CodeCompanion through a robust event-driven architecture:
Initialization and Storage Management:
{data_path}/codecompanion-history/
Chat Lifecycle Integration:
Hooks into CodeCompanionChatCreated
event to:
Monitors CodeCompanionChatSubmitted
events to:
Title Generation System:
State Management:
UI Components:
Data Flow:
Special thanks to Oli Morris for creating the amazing CodeCompanion.nvim plugin - a highly configurable and powerful coding assistant for Neovim.
MIT
No configuration available
Related projects feature coming soon
Will recommend related projects based on sub-categories