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
44 changes: 32 additions & 12 deletions lua/opencode/api.lua
Original file line number Diff line number Diff line change
Expand Up @@ -927,32 +927,40 @@ function M.redo()
end

---@param answer? 'once'|'always'|'reject'
function M.respond_to_permission(answer)
---@param permission? OpencodePermission
function M.respond_to_permission(answer, permission)
answer = answer or 'once'
if not state.current_permission then

local permission_window = require('opencode.ui.permission_window')
local current_permission = permission or permission_window.get_current_permission()

if not current_permission then
vim.notify('No permission request to accept', vim.log.levels.WARN)
return
end

state.api_client
:respond_to_permission(state.current_permission.sessionID, state.current_permission.id, { response = answer })
:respond_to_permission(current_permission.sessionID, current_permission.id, { response = answer })
:catch(function(err)
vim.schedule(function()
vim.notify('Failed to reply to permission: ' .. vim.inspect(err), vim.log.levels.ERROR)
end)
end)
end

function M.permission_accept()
M.respond_to_permission('once')
---@param permission? OpencodePermission
function M.permission_accept(permission)
M.respond_to_permission('once', permission)
end

function M.permission_accept_all()
M.respond_to_permission('always')
---@param permission? OpencodePermission
function M.permission_accept_all(permission)
M.respond_to_permission('always', permission)
end

function M.permission_deny()
M.respond_to_permission('reject')
---@param permission? OpencodePermission
function M.permission_deny(permission)
M.respond_to_permission('reject', permission)
end

function M.toggle_tool_output()
Expand Down Expand Up @@ -1262,12 +1270,24 @@ M.commands = {
completions = { 'accept', 'accept_all', 'deny' },
fn = function(args)
local subcmd = args[1]
local index = tonumber(args[2])
local permission = nil
if index then
local permission_window = require('opencode.ui.permission_window')
local permissions = permission_window.get_all_permissions()
if not permissions or not permissions[index] then
vim.notify('Invalid permission index: ' .. tostring(index), vim.log.levels.ERROR)
return
end
permission = permissions[index]
end

if subcmd == 'accept' then
M.permission_accept()
M.permission_accept(permission)
elseif subcmd == 'accept_all' then
M.permission_accept_all()
M.permission_accept_all(permission)
elseif subcmd == 'deny' then
M.permission_deny()
M.permission_deny(permission)
else
local valid_subcmds = table.concat(M.commands.permission.completions or {}, ', ')
vim.notify('Invalid permission subcommand. Use: ' .. valid_subcmds, vim.log.levels.ERROR)
Expand Down
2 changes: 1 addition & 1 deletion lua/opencode/context/base_context.lua
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ M.get_git_diff = Promise.async(function(context_config)
return nil
end

return Promise.system({ 'git', 'diff', '--cached' }):and_then(function(output)
return Promise.system({ 'git', 'diff', '--cached', '--minimal' }):and_then(function(output)
if output == '' then
return nil
end
Expand Down
15 changes: 9 additions & 6 deletions lua/opencode/core.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ local util = require('opencode.util')
local config = require('opencode.config')
local image_handler = require('opencode.image_handler')
local Promise = require('opencode.promise')
local permission_window = require('opencode.ui.permission_window')

local M = {}
M._abort_count = 0
Expand Down Expand Up @@ -277,9 +278,11 @@ M.cancel = Promise.async(function()
if state.is_running() then
M._abort_count = M._abort_count + 1

-- if there's a current permission, reject it
if state.current_permission then
require('opencode.api').permission_deny()
local permissions = state.pending_permissions or {}
if #permissions and state.api_client then
for _, permission in ipairs(permissions) do
require('opencode.api').permission_deny(permission)
end
end

local ok, result = pcall(function()
Expand Down Expand Up @@ -348,7 +351,7 @@ M.opencode_ok = Promise.async(function()
end)

local function on_opencode_server()
state.current_permission = nil
permission_window.clear_all()
end

--- Switches the current mode to the specified agent.
Expand Down Expand Up @@ -439,7 +442,7 @@ M._on_user_message_count_change = Promise.async(function(_, new, old)
end)

M._on_current_permission_change = Promise.async(function(_, new, old)
local permission_requested = old == nil and new ~= nil
local permission_requested = #old < #new
if config.hooks and config.hooks.on_permission_requested and permission_requested then
local local_session = (state.active_session and state.active_session.id)
and session.get_by_id(state.active_session.id):await()
Expand All @@ -457,7 +460,7 @@ end
function M.setup()
state.subscribe('opencode_server', on_opencode_server)
state.subscribe('user_message_count', M._on_user_message_count_change)
state.subscribe('current_permission', M._on_current_permission_change)
state.subscribe('pending_permissions', M._on_current_permission_change)

vim.schedule(function()
M.opencode_ok()
Expand Down
2 changes: 1 addition & 1 deletion lua/opencode/event_manager.lua
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ local util = require('opencode.util')

--- @class EventPermissionReplied
--- @field type "permission.replied"
--- @field properties {sessionID: string, permissionID: string, response: string}
--- @field properties {sessionID: string, permissionID?: string, requestID?: string, response: string}

--- @class EventFileEdited
--- @field type "file.edited"
Expand Down
35 changes: 0 additions & 35 deletions lua/opencode/keymap.lua
Original file line number Diff line number Diff line change
Expand Up @@ -45,39 +45,4 @@ function M.setup_window_keymaps(keymap_config, buf_id)
process_keymap_entry(keymap_config or {}, { 'n' }, { silent = true, buffer = buf_id })
end

---Add permission keymaps if permissions are being requested,
---otherwise remove them
---@param buf any
function M.toggle_permission_keymap(buf)
if not vim.api.nvim_buf_is_valid(buf) then
return
end
local state = require('opencode.state')
local config = require('opencode.config')
local api = require('opencode.api')

local permission_config = config.keymap.permission
if not permission_config then
return
end

if state.current_permission then
for action, key in pairs(permission_config) do
local api_func = api['permission_' .. action]
if key and api_func then
vim.keymap.set({ 'n', 'i' }, key, api_func, { buffer = buf, silent = true })
end
end
return
end

-- not requesting permissions, clear keymaps
for _, key in pairs(permission_config) do
if key then
pcall(vim.api.nvim_buf_del_keymap, buf, 'n', key)
pcall(vim.api.nvim_buf_del_keymap, buf, 'i', key)
end
end
end

return M
4 changes: 2 additions & 2 deletions lua/opencode/state.lua
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
---@field messages OpencodeMessage[]|nil
---@field current_message OpencodeMessage|nil
---@field last_user_message OpencodeMessage|nil
---@field current_permission OpencodePermission|nil
---@field pending_permissions OpencodePermission[]
---@field cost number
---@field tokens_count number
---@field job_count number
Expand Down Expand Up @@ -78,7 +78,7 @@ local _state = {
messages = nil,
current_message = nil,
last_user_message = nil,
current_permission = nil,
pending_permissions = {},
cost = 0,
tokens_count = 0,
-- job
Expand Down
67 changes: 7 additions & 60 deletions lua/opencode/ui/formatter.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ local state = require('opencode.state')
local config = require('opencode.config')
local snapshot = require('opencode.snapshot')
local mention = require('opencode.ui.mention')
local permission_window = require('opencode.ui.permission_window')

local M = {}

Expand Down Expand Up @@ -54,43 +55,6 @@ function M._format_reasoning(output, part)
end
end

function M._handle_permission_request(output, part)
if part.state and part.state.status == 'error' and part.state.error then
if part.state.error:match('rejected permission') then
state.current_permission = nil
else
vim.notify('Unknown part state error: ' .. part.state.error)
end
return
end

M._format_permission_request(output)
end

function M._format_permission_request(output)
local keys

if require('opencode.ui.ui').is_opencode_focused() then
keys = {
config.keymap.permission.accept,
config.keymap.permission.accept_all,
config.keymap.permission.deny,
}
else
keys = {
config.get_key_for_function('editor', 'permission_accept'),
config.get_key_for_function('editor', 'permission_accept_all'),
config.get_key_for_function('editor', 'permission_deny'),
}
end

output:add_empty_line()
output:add_line('> [!WARNING] Permission required to run this tool.')
output:add_line('>')
output:add_line(('> Accept `%s` Always `%s` Deny `%s`'):format(unpack(keys)))
output:add_empty_line()
end

---Calculate statistics for reverted messages and tool calls
---@param messages {info: MessageInfo, parts: OpencodeMessagePart[]}[] All messages in the session
---@param revert_index number Index of the message where revert occurred
Expand Down Expand Up @@ -646,10 +610,6 @@ function M._format_tool(output, part)
local metadata = part.state.metadata or {}
local tool_output = part.state.output or ''

if state.current_permission and state.current_permission.messageID == part.messageID then
metadata = state.current_permission.metadata or metadata
end

if tool == 'bash' then
M._format_bash_tool(output, input --[[@as BashToolInput]], metadata --[[@as BashToolMetadata]])
elseif tool == 'read' or tool == 'edit' or tool == 'write' then
Expand Down Expand Up @@ -681,25 +641,6 @@ function M._format_tool(output, part)
M._format_callout(output, 'ERROR', part.state.input.error)
end

if
state.current_permission
and (
(
state.current_permission.tool
and state.current_permission.tool.callID == part.callID
and state.current_permission.tool.messageID == part.messageID
)
---@TODO this is for backward compatibility, remove later
or (
not state.current_permission.tool
and state.current_permission.messageID == part.messageID
and state.current_permission.callID == part.callID
)
)
then
M._handle_permission_request(output, part)
end

local end_line = output:get_line_count()
if end_line - start_line > 1 then
M._add_vertical_border(output, start_line, end_line, 'OpencodeToolBorder', -1)
Expand Down Expand Up @@ -893,6 +834,12 @@ function M.format_part(part, message, is_last_part)
M._format_patch(output, part)
content_added = true
end
elseif role == 'system' then
if part.type == 'permissions-display' then
local text = table.concat(permission_window.get_display_lines(), '\n')
output:add_lines(vim.split(vim.trim(text), '\n'))
content_added = true
end
end

if content_added then
Expand Down
4 changes: 0 additions & 4 deletions lua/opencode/ui/input_window.lua
Original file line number Diff line number Diff line change
Expand Up @@ -385,10 +385,6 @@ function M.setup_autocmds(windows, group)
require('opencode.ui.context_bar').render()
end,
})

state.subscribe('current_permission', function()
require('opencode.keymap').toggle_permission_keymap(windows.input_buf)
end)
end

---Toggle the input window visibility (hide/show)
Expand Down
16 changes: 12 additions & 4 deletions lua/opencode/ui/output_window.lua
Original file line number Diff line number Diff line change
Expand Up @@ -214,10 +214,6 @@ function M.setup_autocmds(windows, group)
end,
})

state.subscribe('current_permission', function()
require('opencode.keymap').toggle_permission_keymap(windows.output_buf)
end)

-- Track scroll position when window is scrolled
vim.api.nvim_create_autocmd('WinScrolled', {
group = group,
Expand All @@ -236,4 +232,16 @@ function M.clear()
M.viewport_at_bottom = true
end

---Get the output buffer
---@return integer|nil Buffer ID
function M.get_buf()
return state.windows and state.windows.output_buf
end

---Trigger a re-render by calling the renderer
function M.render()
local renderer = require('opencode.ui.renderer')
renderer._render_all_messages()
end

return M
Empty file.
Loading