Skip to content

Commit fa26e86

Browse files
committed
feat: select from available opencode servers
#118 #150 #119 #105
1 parent a847e5e commit fa26e86

16 files changed

Lines changed: 410 additions & 308 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Integrate the [opencode](https://github.com/sst/opencode) AI assistant with Neov
66

77
## ✨ Features
88

9-
- Auto-connect to _any_ `opencode` running in Neovim's CWD, or provide an integrated instance.
9+
- Connect to _any_ `opencode`s running in Neovim's CWD, or provide an integrated instance.
1010
- Share editor context (buffer, cursor, selection, diagnostics, etc.).
1111
- Input prompts with completions, highlights, and normal-mode support.
1212
- Select prompts from a library and define your own.
@@ -109,7 +109,7 @@ Select or reference prompts to review, explain, and improve your code:
109109

110110
### Provider
111111

112-
You can manually run `opencode` in Neovim's CWD however you like and `opencode.nvim` will find it!
112+
You can manually run `opencode`s in Neovim's CWD however you like and `opencode.nvim` will find them!
113113

114114
If `opencode.nvim` can't find an existing `opencode`, it uses the configured provider (defaulting based on availability) to manage one for you.
115115

lua/opencode.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ local M = {}
44
M.ask = require("opencode.ui.ask").ask
55
M.select = require("opencode.ui.select").select
66
M.select_session = require("opencode.ui.select_session").select_session
7+
M.select_server = require("opencode.ui.select_server").select_server
78

89
M.prompt = require("opencode.api.prompt").prompt
910
M.operator = require("opencode.api.operator").operator

lua/opencode/api/command.lua

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ local M = {}
2424
---@param command opencode.Command|string The command to send. Can be built-in or reference your custom commands.
2525
function M.command(command)
2626
require("opencode.cli.server")
27-
.get_port()
28-
:next(function(port) ---@param port number
27+
.get()
28+
:next(function(server) ---@param server opencode.cli.server.Server
2929
-- No need to register SSE here - commands don't trigger any.
3030
-- (except maybe the `input_*` commands? but no reason for user to use those).
31-
require("opencode.cli.client").tui_execute_command(command, port)
31+
require("opencode.cli.client").tui_execute_command(command, server.port)
3232
end)
3333
:catch(function(err)
3434
vim.notify(err, vim.log.levels.ERROR, { title = "opencode" })

lua/opencode/api/prompt.lua

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,15 @@ function M.prompt(prompt, opts)
2323
context = opts and opts.context or require("opencode.context").new(),
2424
}
2525

26+
local Promise = require("opencode.promise")
2627
require("opencode.cli.server")
27-
.get_port()
28+
.get()
29+
:next(function(server) ---@param server opencode.cli.server.Server
30+
return server.port
31+
end)
2832
:next(function(port) ---@param port number
2933
if opts.clear then
30-
return require("opencode.promise").new(function(resolve)
34+
return Promise.new(function(resolve)
3135
require("opencode.cli.client").tui_execute_command("prompt.clear", port, function()
3236
resolve(port)
3337
end)
@@ -38,24 +42,21 @@ function M.prompt(prompt, opts)
3842
:next(function(port) ---@param port number
3943
local rendered = opts.context:render(prompt)
4044
local plaintext = opts.context.plaintext(rendered.output)
41-
return require("opencode.promise").new(function(resolve)
45+
return Promise.new(function(resolve)
4246
require("opencode.cli.client").tui_append_prompt(plaintext, port, function()
4347
resolve(port)
4448
end)
4549
end)
4650
end)
4751
:next(function(port) ---@param port number
48-
require("opencode.events").subscribe()
49-
5052
if opts.submit then
5153
require("opencode.cli.client").tui_execute_command("prompt.submit", port)
5254
end
5355

5456
return port
5557
end)
5658
:catch(function(err)
57-
vim.notify(err, vim.log.levels.ERROR, { title = "opencode" })
58-
return true
59+
return vim.notify(err, vim.log.levels.ERROR, { title = "opencode" })
5960
end)
6061
:finally(function()
6162
opts.context:clear()

lua/opencode/cli/client.lua

Lines changed: 38 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,6 @@
33
--- - [implementation](https://github.com/sst/opencode/blob/dev/packages/opencode/src/server/server.ts)
44
local M = {}
55

6-
local sse_state = {
7-
-- Track the port - `opencode` may have restarted, usually on a new port
8-
port = nil,
9-
---@type number|nil
10-
job_id = nil,
11-
}
12-
136
---Generate a UUID v4 (cross-platform, no external dependencies)
147
---@return string UUID in format xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
158
local function generate_uuid()
@@ -50,10 +43,11 @@ end
5043

5144
---@param url string
5245
---@param method string
53-
---@param body table|nil
54-
---@param callback fun(response: table)|nil
46+
---@param body table?
47+
---@param on_success fun(response: table)?
48+
---@param on_error fun(code: number, msg: string?)?
5549
---@return number job_id
56-
local function curl(url, method, body, callback)
50+
local function curl(url, method, body, on_success, on_error)
5751
local command = {
5852
"curl",
5953
"-s",
@@ -86,8 +80,8 @@ local function curl(url, method, body, callback)
8680
vim.schedule(function()
8781
local ok, response = pcall(vim.fn.json_decode, full_event)
8882
if ok then
89-
if callback then
90-
callback(response)
83+
if on_success then
84+
on_success(response)
9185
end
9286
else
9387
vim.notify(
@@ -132,12 +126,17 @@ local function curl(url, method, body, callback)
132126
-- Process any remaining buffered data.
133127
process_response_buffer()
134128
elseif code ~= 18 and code ~= 143 then
135-
-- 18 = connection closed, 143 = SIGTERM (manual disconnect)
136-
local error_message = "curl command failed with exit code: "
137-
.. code
138-
.. "\nstderr:\n"
139-
.. (#stderr_lines > 0 and table.concat(stderr_lines, "\n") or "<none>")
140-
vim.notify(error_message, vim.log.levels.ERROR, { title = "opencode" })
129+
local stderr_message = #stderr_lines > 0 and table.concat(stderr_lines, "\n") or nil
130+
if on_error then
131+
on_error(code, stderr_message)
132+
else
133+
-- 18 = connection closed, 143 = SIGTERM (manual disconnect)
134+
local error_message = "curl command failed with exit code: "
135+
.. code
136+
.. "\nstderr:\n"
137+
.. (stderr_message or "<none>")
138+
vim.notify(error_message, vim.log.levels.ERROR, { title = "opencode" })
139+
end
141140
end
142141
end,
143142
})
@@ -147,11 +146,12 @@ end
147146
---@param port number
148147
---@param path string
149148
---@param method "GET"|"POST"
150-
---@param body table|nil
151-
---@param callback fun(response: table)|nil
149+
---@param body table?
150+
---@param on_success fun(response: table)?
151+
---@param on_error fun(code: number, msg: string?)?
152152
---@return number job_id
153-
function M.call(port, path, method, body, callback)
154-
return curl("http://localhost:" .. port .. path, method, body, callback)
153+
function M.call(port, path, method, body, on_success, on_error)
154+
return curl("http://localhost:" .. port .. path, method, body, on_success, on_error)
155155
end
156156

157157
---@param text string
@@ -243,6 +243,16 @@ function M.get_sessions(port, callback)
243243
M.call(port, "/session", "GET", nil, callback)
244244
end
245245

246+
---@class opencode.cli.client.SessionStatus
247+
248+
---Get sessions' status from `opencode`.
249+
---
250+
---@param port number
251+
---@param callback fun(statuses: opencode.cli.client.SessionStatus[])
252+
function M.get_sessions_status(port, callback)
253+
M.call(port, "/session/status", "GET", nil, callback)
254+
end
255+
246256
---Select session in `opencode`.
247257
---
248258
---@param port number
@@ -256,27 +266,10 @@ end
256266
---@field worktree string
257267

258268
---@param port number
259-
---@return opencode.cli.client.PathResponse
260-
function M.get_path(port)
261-
-- Query each port synchronously for working directory
262-
-- TODO: Migrate to align with async paradigm used elsewhere
263-
local curl_result = vim
264-
.system({
265-
"curl",
266-
"-s",
267-
"--connect-timeout",
268-
"1",
269-
"http://localhost:" .. port .. "/path",
270-
})
271-
:wait()
272-
require("opencode.util").check_system_call(curl_result, "curl")
273-
274-
local path_ok, path_data = pcall(vim.fn.json_decode, curl_result.stdout)
275-
if path_ok and (path_data.directory or path_data.worktree) then
276-
return path_data
277-
else
278-
error("Failed to parse `opencode` CWD data: " .. curl_result.stdout, 0)
279-
end
269+
---@param on_success fun(response: opencode.cli.client.PathResponse)
270+
---@param on_error fun()
271+
function M.get_path(port, on_success, on_error)
272+
M.call(port, "/path", "GET", nil, on_success, on_error)
280273
end
281274

282275
---@class opencode.cli.client.Event
@@ -288,28 +281,9 @@ end
288281
---
289282
---@param port number
290283
---@param callback fun(response: opencode.cli.client.Event)|nil
284+
---@return number job_id
291285
function M.sse_subscribe(port, callback)
292-
if sse_state.port ~= port then
293-
if sse_state.job_id then
294-
vim.fn.jobstop(sse_state.job_id)
295-
end
296-
297-
sse_state = {
298-
port = port,
299-
job_id = M.call(port, "/event", "GET", nil, callback),
300-
}
301-
end
302-
end
303-
304-
function M.sse_unsubscribe()
305-
if sse_state.job_id then
306-
vim.fn.jobstop(sse_state.job_id)
307-
end
308-
309-
sse_state = {
310-
port = nil,
311-
job_id = nil,
312-
}
286+
return M.call(port, "/event", "GET", nil, callback)
313287
end
314288

315289
return M

0 commit comments

Comments
 (0)