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
15 changes: 12 additions & 3 deletions lua/opencode/core.lua
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,6 @@ M.open = Promise.async(function(opts)
state.last_sent_context = nil
context.unload_attachments()

state.current_model = nil
state.current_mode = nil
M.ensure_current_mode():await()

state.active_session = M.create_new_session():await()
Expand Down Expand Up @@ -267,6 +265,12 @@ function M.configure_provider()
local model_str = string.format('%s/%s', selection.provider, selection.model)
state.current_model = model_str

if state.current_mode then
local mode_map = vim.deepcopy(state.user_mode_model_map)
mode_map[state.current_mode] = model_str
state.user_mode_model_map = mode_map
end

if state.windows then
ui.focus_input()
else
Expand Down Expand Up @@ -456,8 +460,13 @@ M.switch_to_mode = Promise.async(function(mode)

local agent_config = opencode_config and opencode_config.agent or {}
local mode_config = agent_config[mode] or {}
if mode_config.model and mode_config.model ~= '' then

if state.user_mode_model_map[mode] then
state.current_model = state.user_mode_model_map[mode]
elseif mode_config.model and mode_config.model ~= '' then
state.current_model = mode_config.model
elseif opencode_config and opencode_config.model and opencode_config.model ~= '' then
state.current_model = opencode_config.model
end
return true
end)
Expand Down
2 changes: 2 additions & 0 deletions lua/opencode/state.lua
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
---@field active_session Session|nil
---@field restore_points RestorePoint[]
---@field current_model string|nil
---@field user_mode_model_map table<string, string>
---@field current_model_info table|nil
---@field current_variant string|nil
---@field messages OpencodeMessage[]|nil
Expand Down Expand Up @@ -76,6 +77,7 @@ local _state = {
active_session = nil,
restore_points = {},
current_model = nil,
user_mode_model_map = {},
current_model_info = nil,
current_variant = nil,
-- messages
Expand Down
73 changes: 50 additions & 23 deletions tests/unit/core_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -481,13 +481,15 @@ describe('opencode.core', function()
model = 'anthropic/claude-3-opus',
},
},
model = 'gpt-4',
})

stub(config_file, 'get_opencode_agents').returns(agents_promise)
stub(config_file, 'get_opencode_config').returns(config_promise)

state.current_mode = nil
state.current_model = nil
state.user_mode_model_map = {}

local promise = core.switch_to_mode('custom')
local success = promise:wait()
Expand All @@ -500,57 +502,82 @@ describe('opencode.core', function()
config_file.get_opencode_config:revert()
end)

it('does not change current model when mode has no model configured', function()
it('returns false when mode is invalid', function()
local Promise = require('opencode.promise')
local agents_promise = Promise.new()
agents_promise:resolve({ 'plan', 'build' })

stub(config_file, 'get_opencode_agents').returns(agents_promise)

local promise = core.switch_to_mode('nonexistent')
local success = promise:wait()

assert.is_false(success)

config_file.get_opencode_agents:revert()
end)

it('returns false when mode is empty', function()
local promise = core.switch_to_mode('')
local success = promise:wait()
assert.is_false(success)

promise = core.switch_to_mode(nil)
success = promise:wait()
assert.is_false(success)
end)

it('respects user_mode_model_map priority: uses model stored in mode_model_map for mode', function()
local Promise = require('opencode.promise')
local agents_promise = Promise.new()
agents_promise:resolve({ 'plan', 'build' })
local config_promise = Promise.new()
config_promise:resolve({
agent = {
plan = {},
plan = { model = 'gpt-4' },
},
model = 'gpt-3',
})

stub(config_file, 'get_opencode_agents').returns(agents_promise)
stub(config_file, 'get_opencode_config').returns(config_promise)

state.current_mode = nil
state.current_model = 'existing/model'
state.current_model = 'should-be-overridden'
state.user_mode_model_map = { plan = 'anthropic/claude-3-haiku' }

local promise = core.switch_to_mode('plan')
local success = promise:wait()

assert.is_true(success)
assert.equal('plan', state.current_mode)
assert.equal('existing/model', state.current_model)
assert.equal('anthropic/claude-3-haiku', state.current_model)

config_file.get_opencode_agents:revert()
config_file.get_opencode_config:revert()
end)

it('returns false when mode is invalid', function()
it('falls back to config model if nothing else matches', function()
local Promise = require('opencode.promise')
local agents_promise = Promise.new()
agents_promise:resolve({ 'plan', 'build' })

local config_promise = Promise.new()
config_promise:resolve({
agent = {
plan = {},
},
model = 'default-model',
})
stub(config_file, 'get_opencode_agents').returns(agents_promise)

local promise = core.switch_to_mode('nonexistent')
stub(config_file, 'get_opencode_config').returns(config_promise)
state.current_mode = nil
state.current_model = 'old-model'
state.user_mode_model_map = {}
local promise = core.switch_to_mode('plan')
local success = promise:wait()

assert.is_false(success)

assert.is_true(success)
assert.equal('plan', state.current_mode)
assert.equal('default-model', state.current_model)
config_file.get_opencode_agents:revert()
end)

it('returns false when mode is empty', function()
local promise = core.switch_to_mode('')
local success = promise:wait()
assert.is_false(success)

promise = core.switch_to_mode(nil)
success = promise:wait()
assert.is_false(success)
config_file.get_opencode_config:revert()
end)
end)
end)