Skip to content

Commit 71c519b

Browse files
authored
fix: display cost during streaming, guard winbar against nil/zero crashes, and async providers for percentage (#379)
* fix: display cost during streaming and guard against nil/zero overwrite - Extract token/cost from step-finish parts before early return so cost appears before the chat fully completes (events.lua) - Reject zero/nil cost in set_cost to preserve last known non-zero amount when a new chat starts or is cancelled (renderer.lua) - Fix nil check in switch_mode: util.index_of returns nil not -1, causing arithmetic error when current_mode is not in agents list (agent.lua) * fix(topbar): guard format_token_info against nil/zero cost crashes * fix: load model info synchronously and use %= for winbar right-alignment - Fall back to :wait(200) in get_model_info when :peek() returns nil so providers data is available on first render, enabling percentage - Replace manual space-padding winbar alignment with neovim's %= statusline directive for reliable right-alignment regardless of description character width * fix: trigger topbar re-render when providers load for context limit Replace unreliable :wait() approach with a one-time finally callback on the providers promise that calls topbar.render(). This guarantees the percentage appears even when providers load asynchronously after the initial render.
1 parent 93e6c3c commit 71c519b

5 files changed

Lines changed: 42 additions & 23 deletions

File tree

lua/opencode/commands/handlers/agent.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ M.actions.switch_mode = Promise.async(function()
5555
local modes = config_file.get_opencode_agents():await() --[[@as string[] ]]
5656
local current_index = util.index_of(modes, state.store.get('current_mode'))
5757

58-
if current_index == -1 then
58+
if current_index == nil then
5959
current_index = 0
6060
end
6161

lua/opencode/config_file.lua

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,27 @@ M.get_workspace_snapshot_path = Promise.async(function()
5151
return home .. '/.local/share/opencode/snapshot/' .. project.id
5252
end)
5353

54+
local _providers_render_callback = false
55+
5456
---@return Promise<OpencodeProvidersResponse|nil>
5557
function M.get_opencode_providers()
5658
if not M.providers_promise then
5759
local state = require('opencode.state')
5860
M.providers_promise = state.api_client:list_providers()
5961
end
60-
return M.providers_promise:catch(function(err)
62+
local wrapped = M.providers_promise:catch(function(err)
6163
vim.notify('Error fetching Opencode providers: ' .. vim.inspect(err), vim.log.levels.ERROR)
6264
return nil
6365
end)
66+
if not _providers_render_callback then
67+
_providers_render_callback = true
68+
wrapped:finally(function()
69+
local ok, _ = pcall(function()
70+
require('opencode.ui.topbar').render()
71+
end)
72+
end)
73+
end
74+
return wrapped
6475
end
6576

6677
--- Get model information for a specific provider and model

lua/opencode/state/renderer.lua

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ function M.update_pending_permissions(mutator)
2929
end
3030
---@param cost number
3131
function M.set_cost(cost)
32+
if not cost or cost <= 0 then
33+
return
34+
end
3235
return store.set('cost', cost)
3336
end
3437

@@ -42,7 +45,9 @@ end
4245
function M.set_stats(tokens_count, cost)
4346
return store.batch(function()
4447
store.set('tokens_count', tokens_count)
45-
store.set('cost', cost)
48+
if cost and cost > 0 then
49+
store.set('cost', cost)
50+
end
4651
end)
4752
end
4853

lua/opencode/ui/renderer/events.lua

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,8 +350,15 @@ function M.on_part_updated(properties, revert_index)
350350
end
351351
end
352352

353-
-- step-start / step-finish are bookkeeping only — nothing to render
354353
if part.type == 'step-start' or part.type == 'step-finish' then
354+
if part.type == 'step-finish' and part.tokens then
355+
local tokens = part.tokens
356+
if tokens.input > 0 and part.cost and type(part.cost) == 'number' then
357+
state.renderer.set_stats(tokens.input + tokens.output + tokens.cache.read + tokens.cache.write, part.cost)
358+
elseif tokens.input > 0 then
359+
state.renderer.set_tokens_count(tokens.input + tokens.output + tokens.cache.read + tokens.cache.write)
360+
end
361+
end
355362
return
356363
end
357364

lua/opencode/ui/topbar.lua

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,22 @@ local function format_token_info()
2222
model_info = nil
2323
end
2424
local limit = state.tokens_count and model_info and model_info.limit and model_info.limit.context or 0
25-
table.insert(parts, util.format_number(state.tokens_count) or nil)
25+
local formatted_count = util.format_number(state.tokens_count)
26+
if formatted_count then
27+
table.insert(parts, formatted_count)
28+
end
2629
if limit > 0 then
27-
table.insert(parts, util.format_percentage(state.tokens_count / limit) or nil)
30+
local formatted_pct = util.format_percentage(state.tokens_count / limit)
31+
if formatted_pct then
32+
table.insert(parts, formatted_pct)
33+
end
2834
end
2935
end
30-
if config.ui.display_cost and state.cost then
31-
table.insert(parts, util.format_cost(state.cost) or nil)
36+
if config.ui.display_cost and state.cost and state.cost > 0 then
37+
local formatted_cost = util.format_cost(state.cost)
38+
if formatted_cost then
39+
table.insert(parts, formatted_cost)
40+
end
3241
end
3342
end
3443

@@ -37,21 +46,8 @@ local function format_token_info()
3746
return result
3847
end
3948

40-
local function create_winbar_text(description, token_info, win_width)
41-
local left_content = ''
42-
local right_content = token_info
43-
44-
local desc_width = win_width - util.strdisplaywidth(left_content) - util.strdisplaywidth(right_content)
45-
46-
local desc_formatted
47-
if #description >= desc_width then
48-
local ellipsis = '... '
49-
desc_formatted = description:sub(1, desc_width - #ellipsis) .. ellipsis
50-
else
51-
desc_formatted = description .. string.rep(' ', math.floor(desc_width - #description))
52-
end
53-
54-
return left_content .. desc_formatted .. right_content
49+
local function create_winbar_text(description, token_info, _)
50+
return description .. '%=' .. token_info
5551
end
5652

5753
local function get_session_desc()

0 commit comments

Comments
 (0)