Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ require('opencode').setup({
['<leader>os'] = { 'select_session' }, -- Select and load a opencode session
['<leader>oR'] = { 'rename_session' }, -- Rename current session
['<leader>op'] = { 'configure_provider' }, -- Quick provider and model switch from predefined list
['<leader>oV'] = { 'configure_variant' }, -- Switch model variant for the current model
['<leader>oz'] = { 'toggle_zoom' }, -- Zoom in/out on the Opencode windows
['<leader>ov'] = { 'paste_image'}, -- Paste image from clipboard into current session
['<leader>od'] = { 'diff_open' }, -- Opens a diff tab of a modified file since the last opencode prompt
Expand Down Expand Up @@ -161,6 +162,7 @@ require('opencode').setup({
['<up>'] = { 'prev_prompt_history', mode = { 'n', 'i' } }, -- Navigate to previous prompt in history
['<down>'] = { 'next_prompt_history', mode = { 'n', 'i' } }, -- Navigate to next prompt in history
['<M-m>'] = { 'switch_mode' }, -- Switch between modes (build/plan)
['<M-r>'] = { 'cycle_variant', mode = { 'n', 'i' } }, -- Cycle through available model variants
},
output_window = {
['<esc>'] = { 'close' }, -- Close UI windows
Expand All @@ -169,6 +171,7 @@ require('opencode').setup({
['[['] = { 'prev_message' }, -- Navigate to previous message in the conversation
['<tab>'] = { 'toggle_pane', mode = { 'n', 'i' } }, -- Toggle between input and output panes
['i'] = { 'focus_input', 'n' }, -- Focus on input window and enter insert mode at the end of the input from the output window
['<M-r>'] = { 'cycle_variant', mode = { 'n' } }, -- Cycle through available model variants
['<leader>oS'] = { 'select_child_session' }, -- Select and load a child session
['<leader>oD'] = { 'debug_message' }, -- Open raw message in new buffer for debugging
['<leader>oO'] = { 'debug_output' }, -- Open raw output in new buffer for debugging
Expand Down Expand Up @@ -377,6 +380,18 @@ In the model picker, press **`<C-f>`** to toggle the currently selected model as

No configuration is needed - the plugin respects and updates the OpenCode CLI format automatically.

### Model Variants

Some models support multiple variants (e.g., different context window sizes or optimization modes). The plugin provides convenient ways to switch between available variants for the currently active model.

#### Switching Variants

- **Via picker**: Press `<leader>oV` to open the variant picker showing all available variants for the current model
- **Via cycling**: Press `<M-r>` (Alt+R) in the input or output window to cycle through available variants
- **Via slash command**: Type `/variant` in the input window

When you switch variants, the plugin remembers your selection per model, so the next time you use that model, it will automatically use the last selected variant.

### UI icons (disable emojis or customize)

By default, opencode.nvim uses emojis for icons in the UI. If you prefer a plain, emoji-free interface, you can switch to the `text` preset or override icons individually.
Expand Down Expand Up @@ -552,6 +567,8 @@ The plugin provides the following actions that can be triggered via keymaps, com
| Open timeline picker (navigate/undo/redo/fork to message) | `<leader>oT` | `:Opencode timeline` | `require('opencode.api').timeline()` |
| Browse code references from conversation | `gr` (window) | `:Opencode references` / `/references` | `require('opencode.api').references()` |
| Configure provider and model | `<leader>op` | `:Opencode configure provider` | `require('opencode.api').configure_provider()` |
| Configure model variant | `<leader>oV` | `:Opencode variant` / `/variant` | `require('opencode.api').configure_variant()` |
| Cycle through model variants | `<M-r>` (window) | - | `require('opencode.api').cycle_variant()` |
| Open diff view of changes | `<leader>od` | `:Opencode diff open` | `require('opencode.api').diff_open()` |
| Navigate to next file diff | `<leader>o]` | `:Opencode diff next` | `require('opencode.api').diff_next()` |
| Navigate to previous file diff | `<leader>o[` | `:Opencode diff prev` | `require('opencode.api').diff_prev()` |
Expand Down Expand Up @@ -720,6 +737,7 @@ You can run predefined user commands and built-in slash commands from the input
- `/help` — Show help
- `/mcp` — Show MCP servers
- `/models` — Switch provider/model
- `/variant` — Switch model variant
- `/sessions` — Switch session
- `/child-sessions` — Switch to a child session
- `/agent` — Switch agent/mode
Expand Down
15 changes: 15 additions & 0 deletions lua/opencode/api.lua
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ function M.configure_provider()
core.configure_provider()
end

function M.configure_variant()
core.configure_variant()
end

function M.cycle_variant()
core.cycle_variant()
end

function M.cancel()
core.cancel()
end
Expand Down Expand Up @@ -1205,6 +1213,11 @@ M.commands = {
fn = M.configure_provider,
},

variant = {
desc = 'Switch model variant',
fn = M.configure_variant,
},

run = {
desc = 'Run prompt in current session',
fn = function(args)
Expand Down Expand Up @@ -1334,6 +1347,7 @@ M.slash_commands_map = {
['/history'] = { fn = M.select_history, desc = 'Select from history' },
['/mcp'] = { fn = M.mcp, desc = 'Show MCP server configuration' },
['/models'] = { fn = M.configure_provider, desc = 'Switch provider/model' },
['/variant'] = { fn = M.configure_variant, desc = 'Switch model variant' },
['/new'] = { fn = M.open_input_new_session, desc = 'Create new session' },
['/redo'] = { fn = M.redo, desc = 'Redo last action' },
['/sessions'] = { fn = M.select_session, desc = 'Select session' },
Expand Down Expand Up @@ -1365,6 +1379,7 @@ M.legacy_command_map = {
OpencodeSelectChildSession = 'session child',
OpencodeTogglePane = 'toggle_pane',
OpencodeConfigureProvider = 'models',
OpencodeConfigureVariant = 'variant',
OpencodeRun = 'run',
OpencodeRunNewSession = 'run_new',
OpencodeDiff = 'diff open',
Expand Down
2 changes: 1 addition & 1 deletion lua/opencode/api_client.lua
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ end

--- Create and send a new message to a session
--- @param id string Session ID (required)
--- @param message_data {messageID?: string, model?: {providerID: string, modelID: string}, agent?: string, system?: string, tools?: table<string, boolean>, parts: OpencodeMessagePart[]} Message creation data
--- @param message_data {messageID?: string, model?: {providerID: string, modelID: string}, agent?: string, variant?: string, system?: string, tools?: table<string, boolean>, parts: OpencodeMessagePart[]} Message creation data
--- @param directory string|nil Directory path
--- @return Promise<{info: MessageInfo, parts: OpencodeMessagePart[]}>
function OpencodeApiClient:create_message(id, message_data, directory)
Expand Down
3 changes: 3 additions & 0 deletions lua/opencode/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ M.defaults = {
['<leader>os'] = { 'select_session', desc = 'Select session' },
['<leader>oR'] = { 'rename_session', desc = 'Rename session' },
['<leader>op'] = { 'configure_provider', desc = 'Configure provider' },
['<leader>oV'] = { 'configure_variant', desc = 'Configure model variant' },
['<leader>oz'] = { 'toggle_zoom', desc = 'Toggle zoom' },
['<leader>ov'] = { 'paste_image', desc = 'Paste image from clipboard' },
['<leader>od'] = { 'diff_open', desc = 'Open diff view' },
Expand Down Expand Up @@ -55,6 +56,7 @@ M.defaults = {
['i'] = { 'focus_input' },
['gr'] = { 'references', desc = 'Browse code references' },
['<M-i>'] = { 'toggle_input', mode = { 'n' }, desc = 'Toggle input window' },
['<M-r>'] = { 'cycle_variant', mode = { 'n' } },
['<leader>oS'] = { 'select_child_session' },
['<leader>oD'] = { 'debug_message' },
['<leader>oO'] = { 'debug_output' },
Expand All @@ -73,6 +75,7 @@ M.defaults = {
['<up>'] = { 'prev_prompt_history', mode = { 'n', 'i' } },
['<down>'] = { 'next_prompt_history', mode = { 'n', 'i' } },
['<M-m>'] = { 'switch_mode', mode = { 'n', 'i' } },
['<M-r>'] = { 'cycle_variant', mode = { 'n', 'i' } },
['<M-i>'] = { 'toggle_input', mode = { 'n', 'i' }, desc = 'Toggle input window' },
['gr'] = { 'references', desc = 'Browse code references' },
['<leader>oS'] = { 'select_child_session' },
Expand Down
4 changes: 4 additions & 0 deletions lua/opencode/config_file.lua
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ function M.get_opencode_providers()
end)
end

--- Get model information for a specific provider and model
--- @param provider string Provider ID
--- @param model string Model ID
--- @return OpencodeModel|nil Model information with variants
M.get_model_info = function(provider, model)
local providers_response = M.get_opencode_providers():peek()

Expand Down
103 changes: 100 additions & 3 deletions lua/opencode/core.lua
Original file line number Diff line number Diff line change
Expand Up @@ -154,13 +154,18 @@ M.send_message = Promise.async(function(prompt, opts)
context.load()
opts.model = opts.model or M.initialize_current_model():await()
opts.agent = opts.agent or state.current_mode or config.default_mode

opts.variant = opts.variant or state.current_variant
local params = {}

if opts.model then
local provider, model = opts.model:match('^(.-)/(.+)$')
params.model = { providerID = provider, modelID = model }
state.current_model = opts.model

if opts.variant then
params.variant = opts.variant
state.current_variant = opts.variant
end
end

if opts.agent then
Expand Down Expand Up @@ -255,7 +260,7 @@ function M.before_run(opts)
end

function M.configure_provider()
require('opencode.provider').select(function(selection)
require('opencode.model_picker').select(function(selection)
if not selection then
if state.windows then
ui.focus_input()
Expand All @@ -268,11 +273,86 @@ function M.configure_provider()
if state.windows then
ui.focus_input()
else
vim.notify('Changed provider to ' .. selection.display, vim.log.levels.INFO)
vim.notify('Changed provider to ' .. model_str, vim.log.levels.INFO)
end
end)
end

function M.configure_variant()
require('opencode.variant_picker').select(function(selection)
if not selection then
if state.windows then
ui.focus_input()
end
return
end

state.current_variant = selection.name

if state.windows then
ui.focus_input()
else
vim.notify('Changed variant to ' .. selection.name, vim.log.levels.INFO)
end
end)
end

M.cycle_variant = Promise.async(function()
if not state.current_model then
vim.notify('No model selected', vim.log.levels.WARN)
return
end

local provider, model = state.current_model:match('^(.-)/(.+)$')
if not provider or not model then
return
end

local config_file = require('opencode.config_file')
local model_info = config_file.get_model_info(provider, model)

if not model_info or not model_info.variants then
vim.notify('Current model does not support variants', vim.log.levels.WARN)
return
end

local variants = {}
for variant_name, _ in pairs(model_info.variants) do
table.insert(variants, variant_name)
end

util.sort_by_priority(variants, function(item)
return item
end, { low = 1, medium = 2, high = 3 })

if #variants == 0 then
return
end

local total_count = #variants + 1

local current_index
if state.current_variant == nil then
current_index = total_count
else
current_index = util.index_of(variants, state.current_variant) or 0
end

local next_index = (current_index % total_count) + 1

local next_variant
if next_index > #variants then
next_variant = nil
else
next_variant = variants[next_index]
end

state.current_variant = next_variant

local model_state = require('opencode.model_state')
model_state.set_variant(provider, model, next_variant)
end)

M.cancel = Promise.async(function()
if state.windows and state.active_session then
if state.is_running() then
Expand Down Expand Up @@ -461,6 +541,23 @@ function M.setup()
state.subscribe('opencode_server', on_opencode_server)
state.subscribe('user_message_count', M._on_user_message_count_change)
state.subscribe('pending_permissions', M._on_current_permission_change)
state.subscribe('current_model', function(key, new_val, old_val)
if new_val ~= old_val then
state.current_variant = nil

-- Load saved variant for the new model
if new_val then
local provider, model = new_val:match('^(.-)/(.+)$')
if provider and model then
local model_state = require('opencode.model_state')
local saved_variant = model_state.get_variant(provider, model)
if saved_variant then
state.current_variant = saved_variant
end
end
end
end
end)

vim.schedule(function()
M.opencode_ok()
Expand Down
Loading