Skip to content

Commit 7f4b114

Browse files
committed
fix(core): invalid model error handling
It looks like there was a change in opencode 1.0 that changed how invalid model errors are surfaced so this change fixes that. Also add unit test coverage for send_message changing state.user_message_count (and fix a bug with it not being updated in the catch) Hopefully fixes #131
1 parent 3cc6e77 commit 7f4b114

File tree

2 files changed

+92
-13
lines changed

2 files changed

+92
-13
lines changed

lua/opencode/core.lua

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -147,31 +147,35 @@ function M.send_message(prompt, opts)
147147
params.parts = context.format_message(prompt, opts.context)
148148
M.before_run(opts)
149149

150-
-- Capture the session ID to ensure we track the message count for the correct session
151150
local session_id = state.active_session.id
152-
local sent_message_count = vim.deepcopy(state.user_message_count)
153-
sent_message_count[session_id] = (sent_message_count[session_id] or 0) + 1
154-
state.user_message_count = sent_message_count
151+
152+
---Helper to update state.user_message_count. Have to deepcopy since it's a table to make
153+
---sure notification events fire. Prevents negative values (in case of an untracked code path)
154+
local function update_sent_message_count(num)
155+
local sent_message_count = vim.deepcopy(state.user_message_count)
156+
local new_value = (sent_message_count[session_id] or 0) + num
157+
sent_message_count[session_id] = new_value >= 0 and new_value or 0
158+
state.user_message_count = sent_message_count
159+
end
160+
161+
update_sent_message_count(1)
155162

156163
state.api_client
157164
:create_message(session_id, params)
158165
:and_then(function(response)
166+
update_sent_message_count(-1)
167+
159168
if not response or not response.info or not response.parts then
160-
-- fall back to full render. incremental render is handled
161-
-- event manager
162-
ui.render_output()
169+
vim.notify('Invalid response from opencode: ' .. vim.inspect(response), vim.log.levels.ERROR)
170+
M.cancel()
171+
return
163172
end
164173

165-
local received_message_count = vim.deepcopy(state.user_message_count)
166-
received_message_count[response.info.sessionID] = (received_message_count[response.info.sessionID] ~= nil)
167-
and (received_message_count[response.info.sessionID] - 1)
168-
or 0
169-
state.user_message_count = received_message_count
170-
171174
M.after_run(prompt)
172175
end)
173176
:catch(function(err)
174177
vim.notify('Error sending message to session: ' .. vim.inspect(err), vim.log.levels.ERROR)
178+
update_sent_message_count(-1)
175179
M.cancel()
176180
end)
177181
end

tests/unit/core_spec.lua

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,81 @@ describe('opencode.core', function()
271271
assert.equal(state.current_model, 'test/model')
272272
state.api_client.create_message = orig
273273
end)
274+
275+
it('increments and decrements user_message_count correctly', function()
276+
state.windows = { mock = 'windows' }
277+
state.active_session = { id = 'sess1' }
278+
state.user_message_count = {}
279+
280+
-- Capture the count at different stages
281+
local count_before = state.user_message_count['sess1'] or 0
282+
local count_during = nil
283+
local count_after = nil
284+
285+
local orig = state.api_client.create_message
286+
state.api_client.create_message = function(_, sid, params)
287+
-- Capture count while message is in flight
288+
count_during = state.user_message_count['sess1']
289+
return Promise.new():resolve({
290+
id = 'm1',
291+
info = { id = 'm1' },
292+
parts = {},
293+
})
294+
end
295+
296+
core.send_message('hello world')
297+
298+
-- Wait for promise to resolve
299+
vim.wait(50, function()
300+
count_after = state.user_message_count['sess1'] or 0
301+
return count_after == 0
302+
end)
303+
304+
-- Verify: starts at 0, increments to 1, then back to 0
305+
assert.equal(0, count_before)
306+
assert.equal(1, count_during)
307+
assert.equal(0, count_after)
308+
309+
state.api_client.create_message = orig
310+
end)
311+
312+
it('decrements user_message_count on error', function()
313+
state.windows = { mock = 'windows' }
314+
state.active_session = { id = 'sess1' }
315+
state.user_message_count = {}
316+
317+
-- Capture the count at different stages
318+
local count_before = state.user_message_count['sess1'] or 0
319+
local count_during = nil
320+
local count_after = nil
321+
322+
local orig = state.api_client.create_message
323+
state.api_client.create_message = function(_, sid, params)
324+
-- Capture count while message is in flight
325+
count_during = state.user_message_count['sess1']
326+
return Promise.new():reject('Test error')
327+
end
328+
329+
-- Stub cancel to prevent it from trying to abort the session
330+
local orig_cancel = core.cancel
331+
stub(core, 'cancel')
332+
333+
core.send_message('hello world')
334+
335+
-- Wait for promise to reject
336+
vim.wait(50, function()
337+
count_after = state.user_message_count['sess1'] or 0
338+
return count_after == 0
339+
end)
340+
341+
-- Verify: starts at 0, increments to 1, then back to 0 even on error
342+
assert.equal(0, count_before)
343+
assert.equal(1, count_during)
344+
assert.equal(0, count_after)
345+
346+
state.api_client.create_message = orig
347+
core.cancel = orig_cancel
348+
end)
274349
end)
275350

276351
describe('opencode_ok (version checks)', function()

0 commit comments

Comments
 (0)