diff --git a/lua/opencode/commands/handlers/workflow.lua b/lua/opencode/commands/handlers/workflow.lua index 2853fa93..9ce431d1 100644 --- a/lua/opencode/commands/handlers/workflow.lua +++ b/lua/opencode/commands/handlers/workflow.lua @@ -11,6 +11,7 @@ local input_window = require('opencode.ui.input_window') local ui = require('opencode.ui.ui') local nvim = vim['api'] local session_runtime = require('opencode.services.session_runtime') +local agent_model = require('opencode.services.agent_model') local M = { actions = {}, @@ -243,6 +244,13 @@ M.actions.run_user_command = Promise.async(function(name, args) local model = command_cfg.model or state.current_model local agent = command_cfg.agent or state.current_mode + if command_cfg.agent then + local available_agents = config_file.get_opencode_agents():await() + if vim.tbl_contains(available_agents, agent) then + agent_model.switch_to_mode(agent) + end + end + local active_session = get_active_session_or_warn('No active session') if not active_session then return diff --git a/lua/opencode/services/agent_model.lua b/lua/opencode/services/agent_model.lua index 0037707f..b29914fa 100644 --- a/lua/opencode/services/agent_model.lua +++ b/lua/opencode/services/agent_model.lua @@ -174,7 +174,10 @@ M.initialize_current_model = Promise.async(function(opts) state.model.set_model(model_str) end if msg.info.mode and state.current_mode ~= msg.info.mode then - state.model.set_mode(msg.info.mode) + local available_agents = config_file.get_opencode_agents():await() + if vim.tbl_contains(available_agents, msg.info.mode) then + state.model.set_mode(msg.info.mode) + end end return state.current_model end diff --git a/lua/opencode/services/messaging.lua b/lua/opencode/services/messaging.lua index 4bd433b5..d31fd313 100644 --- a/lua/opencode/services/messaging.lua +++ b/lua/opencode/services/messaging.lua @@ -2,6 +2,7 @@ local state = require('opencode.state') local context = require('opencode.context') local util = require('opencode.util') local config = require('opencode.config') +local config_file = require('opencode.config_file') local Promise = require('opencode.promise') local log = require('opencode.log') local agent_model = require('opencode.services.agent_model') @@ -48,7 +49,10 @@ M.send_message = Promise.async(function(prompt, opts) if opts.agent then params.agent = opts.agent - state.model.set_mode(opts.agent) + local available_agents = config_file.get_opencode_agents():await() + if vim.tbl_contains(available_agents, opts.agent) then + state.model.set_mode(opts.agent) + end end params.parts = context.format_message(prompt, opts.context):await() diff --git a/tests/unit/api_spec.lua b/tests/unit/api_spec.lua index 0493e9b2..aec1284d 100644 --- a/tests/unit/api_spec.lua +++ b/tests/unit/api_spec.lua @@ -463,6 +463,8 @@ describe('opencode.api', function() stub(api, 'open_input').invokes(function() return resolved('done') end) + local config_file = require('opencode.config_file') + stub(config_file, 'get_opencode_agents').returns(resolved({ 'plan', 'build' })) end) it('invokes run with correct model and agent', function() diff --git a/tests/unit/services_agent_model_spec.lua b/tests/unit/services_agent_model_spec.lua index 0bea00d7..08098af7 100644 --- a/tests/unit/services_agent_model_spec.lua +++ b/tests/unit/services_agent_model_spec.lua @@ -144,6 +144,9 @@ describe('opencode.services.agent_model', function() it('restores the latest session model and mode when explicitly requested', function() state.model.set_model('openai/gpt-4.1') state.model.set_mode('plan') + + stub(config_file, 'get_opencode_agents').returns(Promise.new():resolve({ 'plan', 'build' })) + state.renderer.set_messages({ { info = { @@ -160,5 +163,33 @@ describe('opencode.services.agent_model', function() assert.equal('anthropic/claude-3-opus', model) assert.equal('anthropic/claude-3-opus', state.current_model) assert.equal('build', state.current_mode) + + config_file.get_opencode_agents:revert() + end) + + it('does not restore mode to a hidden agent from messages', function() + state.model.set_model('openai/gpt-4.1') + state.model.set_mode('build') + + stub(config_file, 'get_opencode_agents').returns(Promise.new():resolve({ 'plan', 'build' })) + + state.renderer.set_messages({ + { + info = { + id = 'm1', + providerID = 'anthropic', + modelID = 'claude-3-opus', + mode = 'hidden-xyz', + }, + }, + }) + + local model = agent_model.initialize_current_model({ restore_from_messages = true }):wait() + + assert.equal('anthropic/claude-3-opus', model) + assert.equal('anthropic/claude-3-opus', state.current_model) + assert.equal('build', state.current_mode) + + config_file.get_opencode_agents:revert() end) end) diff --git a/tests/unit/services_messaging_spec.lua b/tests/unit/services_messaging_spec.lua index 234398f7..353988e6 100644 --- a/tests/unit/services_messaging_spec.lua +++ b/tests/unit/services_messaging_spec.lua @@ -7,6 +7,7 @@ loaded.services_messaging_spec = true local messaging = require('opencode.services.messaging') local session_runtime = require('opencode.services.session_runtime') +local config_file = require('opencode.config_file') local state = require('opencode.state') local Promise = require('opencode.promise') local stub = require('luassert.stub') @@ -52,6 +53,8 @@ describe('opencode.services.messaging', function() state.ui.set_windows({ mock = 'windows' }) state.session.set_active({ id = 'sess1' }) + stub(config_file, 'get_opencode_agents').returns(Promise.new():resolve({ 'plan', 'build' })) + local create_called = false state.api_client.create_message = function(_, sid, params) create_called = true @@ -69,6 +72,59 @@ describe('opencode.services.messaging', function() assert.equal(state.current_model, 'test/model') assert.is_true(create_called) state.api_client.create_message = orig + config_file.get_opencode_agents:revert() + end) + + it('does not switch mode when agent is hidden', function() + state.ui.set_windows({ mock = 'windows' }) + state.session.set_active({ id = 'sess1' }) + state.model.set_mode('build') + + stub(config_file, 'get_opencode_agents').returns(Promise.new():resolve({ 'plan', 'build' })) + + local captured_params = nil + local orig = state.api_client.create_message + state.api_client.create_message = function(_, sid, params) + captured_params = params + return Promise.new():resolve({ id = 'm1' }) + end + + messaging.send_message('hello world', { agent = 'hidden-xyz' }) + vim.wait(50, function() + return captured_params ~= nil + end) + + assert.equal('build', state.current_mode) + assert.equal('hidden-xyz', captured_params.agent) + + state.api_client.create_message = orig + config_file.get_opencode_agents:revert() + end) + + it('switches mode when agent is visible', function() + state.ui.set_windows({ mock = 'windows' }) + state.session.set_active({ id = 'sess1' }) + state.model.set_mode('build') + + stub(config_file, 'get_opencode_agents').returns(Promise.new():resolve({ 'plan', 'build' })) + + local captured_params = nil + local orig = state.api_client.create_message + state.api_client.create_message = function(_, sid, params) + captured_params = params + return Promise.new():resolve({ id = 'm1' }) + end + + messaging.send_message('hello world', { agent = 'plan' }) + vim.wait(50, function() + return captured_params ~= nil + end) + + assert.equal('plan', state.current_mode) + assert.equal('plan', captured_params.agent) + + state.api_client.create_message = orig + config_file.get_opencode_agents:revert() end) it('increments and decrements user_message_count correctly', function() diff --git a/tests/unit/services_session_runtime_spec.lua b/tests/unit/services_session_runtime_spec.lua index 1c211a6c..21797da8 100644 --- a/tests/unit/services_session_runtime_spec.lua +++ b/tests/unit/services_session_runtime_spec.lua @@ -650,6 +650,9 @@ describe('opencode.services.session_runtime', function() it('restores the latest session model and mode when explicitly requested', function() state.model.set_model('openai/gpt-4.1') state.model.set_mode('plan') + + stub(config_file, 'get_opencode_agents').returns(Promise.new():resolve({ 'plan', 'build' })) + state.renderer.set_messages({ { info = { @@ -666,6 +669,8 @@ describe('opencode.services.session_runtime', function() assert.equal('anthropic/claude-3-opus', model) assert.equal('anthropic/claude-3-opus', state.current_model) assert.equal('build', state.current_mode) + + config_file.get_opencode_agents:revert() end) end) end)