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
13 changes: 11 additions & 2 deletions lua/opencode/history.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
local log = require('opencode.log')

local M = {}

local cached_history = nil
Expand Down Expand Up @@ -26,12 +28,17 @@ M.write = function(prompt)
end
local file = io.open(path, 'a')
if not file then
log.warn('history: could not open %s for appending', path)
return false
end
local written = file:write(vim.json.encode(prompt) .. '\n')
local closed = file:close()
cached_history = nil
return written ~= nil and closed ~= nil
if written == nil or closed == nil then
log.warn('history: failed to append to %s', path)
return false
end
return true
end

---@return string[]
Expand All @@ -49,7 +56,9 @@ M.read = function()
for line in file:lines() do
if is_json then
local ok, prompt = pcall(vim.json.decode, line)
if ok and type(prompt) == 'string' then
if not ok then
log.warn('history: skipping undecodable line: %s', line)
elseif type(prompt) == 'string' then
lines[#lines + 1] = prompt
end
elseif line:find('%S') then
Expand Down
16 changes: 10 additions & 6 deletions lua/opencode/log.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ local M = {}

local config = require('opencode.config')

local log_path = (config.logging and config.logging.outfile) or vim.fn.stdpath('log') .. '/opencode.log'
local log_levels = { debug = 1, info = 2, warn = 3, error = 4 }
local current_level = log_levels[(config.logging and config.logging.level or 'warn'):lower()] or 3

local function log_path()
return (config.logging and config.logging.outfile) or vim.fn.stdpath('log') .. '/opencode.log'
end

local function caller_info()
local level = 2
Expand All @@ -22,10 +24,12 @@ local function caller_info()
end

local function log(level, msg, ...)
if not config.logging.enabled then
local settings = config.logging or {}
if not settings.enabled then
return
end
if log_levels[level] < current_level then
local threshold = log_levels[(settings.level or 'warn'):lower()] or log_levels.warn
if log_levels[level] < threshold then
return
end
local line = string.format(
Expand All @@ -35,7 +39,7 @@ local function log(level, msg, ...)
caller_info(),
string.format(msg, ...)
)
local file = io.open(log_path, 'a')
local file = io.open(log_path(), 'a')
if file then
file:write(line)
file:close()
Expand All @@ -56,7 +60,7 @@ function M.error(msg, ...)
end

function M.get_path()
return config.logging.enabled and log_path or nil
return config.logging and config.logging.enabled and log_path() or nil
end

--- Emit a user-visible notification and write the same message to the log.
Expand Down
16 changes: 9 additions & 7 deletions lua/opencode/opencode_server.lua
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ local protocol_connection = require('opencode.protocols.connection')
---@field private _stream? {shutdown: fun(self: table)}
---@field private _requests table<table, true>
---@field private _observe? fun(connection: OpencodeServer, ref: {id: string, location?: OpencodeLocation}): OpencodeObservation
---@field private _close_observations? fun(connection: OpencodeServer)
---@field private _close_observations? fun(connection: OpencodeServer, reason: string)
local OpencodeServer = {}
OpencodeServer.__index = OpencodeServer

Expand All @@ -38,7 +38,7 @@ local function ensure_vim_leave_autocmd()
callback = function()
local state = require('opencode.state')
if state.opencode_server then
state.opencode_server:close()
state.opencode_server:close('vim exit')
end
end,
})
Expand Down Expand Up @@ -217,8 +217,9 @@ function OpencodeServer:check_health()
return protocol_connection.check_health(self)
end

---@param reason string Why the connection is going away; stranded prompts report it
---@return Promise<boolean>
function OpencodeServer:close()
function OpencodeServer:close(reason)
if self.shutdown_promise:is_resolved() then
return self.shutdown_promise
end
Expand All @@ -228,7 +229,7 @@ function OpencodeServer:close()
local close_observations = self._close_observations
self._close_observations = nil
if close_observations then
close_observations(self)
close_observations(self, reason or 'unspecified')
end
local requests = self._requests
self._requests = {}
Expand Down Expand Up @@ -258,9 +259,10 @@ function OpencodeServer:close()
return self.shutdown_promise
end

---@param reason? string
---@return Promise<boolean>
function OpencodeServer:shutdown()
return self:close()
function OpencodeServer:shutdown(reason)
return self:close(reason)
end

--- @class OpencodeServerSpawnOpts
Expand Down Expand Up @@ -352,7 +354,7 @@ function OpencodeServer:spawn(opts)
self.job = nil
self.handle = nil
safe_call(opts.on_exit, exit_opts)
self:close()
self:close('server process exited')
end)

self.handle = self.job and self.job.pid
Expand Down
6 changes: 5 additions & 1 deletion lua/opencode/port_mapping.lua
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ local function load()
local content = file:read('*all')
file:close()
local ok, data = pcall(vim.json.decode, content or '')
return ok and data or {}
if not ok then
log.warn('port_mapping: discarding unreadable %s: %s', file_path(), tostring(data))
return {}
end
return data
end

--- @param mappings table<string, PortMapping>
Expand Down
10 changes: 8 additions & 2 deletions lua/opencode/protocols/contract_check.lua
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,12 @@ function M.check(connection)
return nil
end
local ok, spec = pcall(vim.json.decode, response.body)
if not ok or type(spec.paths) ~= 'table' then
if not ok then
log.warn('contract check: could not decode /openapi.json: %s', tostring(spec))
return nil
end
if type(spec.paths) ~= 'table' then
log.warn('contract check: /openapi.json has no paths table')
return nil
end

Expand All @@ -70,7 +75,8 @@ function M.check(connection)
end
return missing
end)
:catch(function()
:catch(function(err)
log.warn('contract check skipped: %s', tostring(err))
return nil
end)
end
Expand Down
23 changes: 19 additions & 4 deletions lua/opencode/protocols/observation.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
local log = require('opencode.log')

local M = {}

local resource_names = {
Expand Down Expand Up @@ -56,7 +58,7 @@ local resource_names = {
---@field on_release_resource? fun(observation: OpencodeObservation, resource: OpencodeObservedResource)
---@field on_unused? fun(observation: OpencodeObservation)
---@field on_stream_error? fun(observation: OpencodeObservation, message: string)
---@field on_close? fun(observation: OpencodeObservation)
---@field on_close? fun(observation: OpencodeObservation, reason: string)

---@class OpencodeMessageSelection
---@field mode? string
Expand Down Expand Up @@ -452,6 +454,7 @@ local function stream_failure(connection, owner, reason)
close_stream(connection)
local message = type(reason) == 'table' and tostring(reason.message or reason.code or 'event stream disconnected')
or tostring(reason or 'event stream disconnected')
log.warn('event stream failed (%s): %s', owner.runtime.name, message)
for _, observation in pairs(connection.observations) do
if observation._runtime.on_stream_error then
observation._runtime.on_stream_error(observation, message)
Expand All @@ -472,7 +475,10 @@ local function decode_record(connection, owner)
stream_failure(connection, owner, 'invalid ' .. owner.runtime.name .. ' event JSON')
return
end
owner.runtime.route_event(connection, event)
local routed, err = pcall(owner.runtime.route_event, connection, event)
if not routed then
log.error('event routing failed (%s): %s', owner.runtime.name, tostring(err))
end
end

local function consume_stream_chunk(connection, owner, chunk)
Expand Down Expand Up @@ -599,6 +605,13 @@ function Observation:_start_resource(resource)
if owns_request() then
self._loading[resource] = nil
self._state.sync[resource] = M.sync_error('operation', err)
log.warn(
'observation %s load failed for %s (%s): %s',
self._session_id,
resource,
self._runtime.name,
tostring(err)
)
self:_notify(resource)
end
end
Expand Down Expand Up @@ -727,11 +740,13 @@ function M.attach(connection, session, state, runtime)
}, Observation)
end

function M.close(connection)
---@param connection table
---@param reason string Why the connection is closing; reaches every admission it strands
function M.close(connection, reason)
close_stream(connection)
for _, observation in pairs(connection.observations) do
if observation._runtime.on_close then
observation._runtime.on_close(observation)
observation._runtime.on_close(observation, reason)
end
end
connection.observations = {}
Expand Down
9 changes: 5 additions & 4 deletions lua/opencode/protocols/v1/observation.lua
Original file line number Diff line number Diff line change
Expand Up @@ -981,8 +981,9 @@ local function track_submission(observation, result)
end

---@param connection table
function M.close(connection)
lifecycle.close(connection)
---@param reason string
function M.close(connection, reason)
lifecycle.close(connection, reason)
end

local function clear_unresolved_mentions(observation)
Expand Down Expand Up @@ -1023,8 +1024,8 @@ function M.new(connection, ref)
on_stream_error = function(current, message)
fail_submissions(current, 'V1 reply completion is unknown: ' .. message)
end,
on_close = function(current)
fail_submissions(current, 'connection closed')
on_close = function(current, reason)
fail_submissions(current, 'connection closed (' .. tostring(reason) .. ')')
clear_unresolved_mentions(current)
end,
})
Expand Down
3 changes: 3 additions & 0 deletions lua/opencode/protocols/v2/normalize.lua
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,9 @@ end
---@param key string|integer
---@return string
local function synthetic_id(owner_id, kind, key)
if owner_id:find(':', 1, true) then
error('V2 observation: message id must not contain ":": ' .. owner_id)
end
return owner_id .. ':' .. kind .. ':' .. tostring(key)
end

Expand Down
9 changes: 5 additions & 4 deletions lua/opencode/protocols/v2/observation.lua
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ function M.new(connection, ref)
end,
on_stream_error = actions.invalidate_submissions,
---@param current OpencodeObservation
on_close = function(current)
on_close = function(current, reason)
---@cast current OpencodeV2Observation
actions.invalidate_submissions(current, 'connection closed')
actions.invalidate_submissions(current, 'connection closed (' .. tostring(reason) .. ')')
end,
})
---@cast observation OpencodeV2Observation
Expand All @@ -68,8 +68,9 @@ function M.new(connection, ref)
end

---@param connection OpencodeV2Connection
function M.close(connection)
lifecycle.close(connection)
---@param reason string
function M.close(connection, reason)
lifecycle.close(connection, reason)
end

return M
2 changes: 1 addition & 1 deletion lua/opencode/server_job.lua
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ function M.spawn_local_server(promise, port, hostname)
end)
:catch(function(err)
log.notify(' Failed to start opencode server' .. vim.inspect(err), vim.log.levels.ERROR)
server:shutdown()
server:shutdown('startup probe failed')
promise:reject(err)
end)
end,
Expand Down
2 changes: 1 addition & 1 deletion lua/opencode/services/agent_model.lua
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ function M.sync_from_server()
M.apply_server_selection(fact and fact.model)
end)
:catch(function(err)
log.debug('Failed to sync session model from server', { error = err })
log.warn('Failed to sync session model from server: %s', tostring(err))
end)
end

Expand Down
Loading
Loading