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
30 changes: 17 additions & 13 deletions lua/opencode/core.lua
Original file line number Diff line number Diff line change
Expand Up @@ -147,31 +147,35 @@ function M.send_message(prompt, opts)
params.parts = context.format_message(prompt, opts.context)
M.before_run(opts)

-- Capture the session ID to ensure we track the message count for the correct session
local session_id = state.active_session.id
local sent_message_count = vim.deepcopy(state.user_message_count)
sent_message_count[session_id] = (sent_message_count[session_id] or 0) + 1
state.user_message_count = sent_message_count

---Helper to update state.user_message_count. Have to deepcopy since it's a table to make
---sure notification events fire. Prevents negative values (in case of an untracked code path)
local function update_sent_message_count(num)
local sent_message_count = vim.deepcopy(state.user_message_count)
local new_value = (sent_message_count[session_id] or 0) + num
sent_message_count[session_id] = new_value >= 0 and new_value or 0
state.user_message_count = sent_message_count
end

update_sent_message_count(1)

state.api_client
:create_message(session_id, params)
:and_then(function(response)
update_sent_message_count(-1)

if not response or not response.info or not response.parts then
-- fall back to full render. incremental render is handled
-- event manager
ui.render_output()
vim.notify('Invalid response from opencode: ' .. vim.inspect(response), vim.log.levels.ERROR)
M.cancel()
return
end

local received_message_count = vim.deepcopy(state.user_message_count)
received_message_count[response.info.sessionID] = (received_message_count[response.info.sessionID] ~= nil)
and (received_message_count[response.info.sessionID] - 1)
or 0
state.user_message_count = received_message_count

M.after_run(prompt)
end)
:catch(function(err)
vim.notify('Error sending message to session: ' .. vim.inspect(err), vim.log.levels.ERROR)
update_sent_message_count(-1)
M.cancel()
end)
end
Expand Down
75 changes: 75 additions & 0 deletions tests/unit/core_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,81 @@ describe('opencode.core', function()
assert.equal(state.current_model, 'test/model')
state.api_client.create_message = orig
end)

it('increments and decrements user_message_count correctly', function()
state.windows = { mock = 'windows' }
state.active_session = { id = 'sess1' }
state.user_message_count = {}

-- Capture the count at different stages
local count_before = state.user_message_count['sess1'] or 0
local count_during = nil
local count_after = nil

local orig = state.api_client.create_message
state.api_client.create_message = function(_, sid, params)
-- Capture count while message is in flight
count_during = state.user_message_count['sess1']
return Promise.new():resolve({
id = 'm1',
info = { id = 'm1' },
parts = {},
})
end

core.send_message('hello world')

-- Wait for promise to resolve
vim.wait(50, function()
count_after = state.user_message_count['sess1'] or 0
return count_after == 0
end)

-- Verify: starts at 0, increments to 1, then back to 0
assert.equal(0, count_before)
assert.equal(1, count_during)
assert.equal(0, count_after)

state.api_client.create_message = orig
end)

it('decrements user_message_count on error', function()
state.windows = { mock = 'windows' }
state.active_session = { id = 'sess1' }
state.user_message_count = {}

-- Capture the count at different stages
local count_before = state.user_message_count['sess1'] or 0
local count_during = nil
local count_after = nil

local orig = state.api_client.create_message
state.api_client.create_message = function(_, sid, params)
-- Capture count while message is in flight
count_during = state.user_message_count['sess1']
return Promise.new():reject('Test error')
end

-- Stub cancel to prevent it from trying to abort the session
local orig_cancel = core.cancel
stub(core, 'cancel')

core.send_message('hello world')

-- Wait for promise to reject
vim.wait(50, function()
count_after = state.user_message_count['sess1'] or 0
return count_after == 0
end)

-- Verify: starts at 0, increments to 1, then back to 0 even on error
assert.equal(0, count_before)
assert.equal(1, count_during)
assert.equal(0, count_after)

state.api_client.create_message = orig
core.cancel = orig_cancel
end)
end)

describe('opencode_ok (version checks)', function()
Expand Down