3
43
Fork
You've already forked opencode.el
16

multi-connection support (fix #33) #35

Open
cmm wants to merge 1 commit from cmm/opencode.el:multi-connection into main
pull from: cmm/opencode.el:multi-connection
merge into: sczi:main
sczi:main
First-time contributor
Copy link

Summary

Replace the single global OpenCode connection with a multi-connection model so one Emacs session can talk to several OpenCode servers.

Disclaimers and status

  • Done with LLM assistance -- but with heavy couching, thorough manual cleanup, and some amount of dogfooding.
  • My usage of the package is, I suspect, relatively pedestrian. Caveat emptor re: dogfooding.
  • Past experience suggests high possibility of run-on fixes in coming days. :/

What changed

  • New opencode-servers defcustom: an alist of named connections, each a plist of :host, :port, :base-url, :username, :password. Default is '((default)).
  • New opencode-find-server-function defcustom: called with a directory, returns a server name from opencode-servers (or nil to fall through to default resolution).
  • opencode-host, opencode-port, opencode-server-username, opencode-server-password are now fallbacks for opencode-servers entries that omit the matching field. Existing single-server configs keep working unchanged.
  • opencode--connection struct now carries per-connection host/port/auth.

Breaking changes / migration

  • opencode-file-edited-functions, opencode-files-finished-editing-functions and opencode-project-files-finished-editing-functions now receive a leading context argument (which is just the connection, but they don't need to know that). Existing hook functions need to add an (ignored or used) first parameter.
  • Users with custom opencode-host / opencode-port / opencode-server-username / opencode-server-password settings need no change — these still work as the fallback for the default ((default)) server entry.
## Summary Replace the single global OpenCode connection with a multi-connection model so one Emacs session can talk to several OpenCode servers. ## Disclaimers and status - Done with LLM assistance -- but with heavy couching, thorough manual cleanup, and some amount of dogfooding. - My usage of the package is, I suspect, relatively pedestrian. Caveat emptor re: dogfooding. - Past experience suggests high possibility of run-on fixes in coming days. :/ ## What changed - New `opencode-servers` defcustom: an alist of named connections, each a plist of `:host`, `:port`, `:base-url`, `:username`, `:password`. Default is `'((default))`. - New `opencode-find-server-function` defcustom: called with a directory, returns a server name from `opencode-servers` (or nil to fall through to default resolution). - `opencode-host`, `opencode-port`, `opencode-server-username`, `opencode-server-password` are now fallbacks for `opencode-servers` entries that omit the matching field. Existing single-server configs keep working unchanged. - `opencode--connection` struct now carries per-connection host/port/auth. ## Breaking changes / migration - `opencode-file-edited-functions`, `opencode-files-finished-editing-functions` and `opencode-project-files-finished-editing-functions` now receive a leading `context` argument (which is just the connection, but they don't need to know that). Existing hook functions need to add an (ignored or used) first parameter. - Users with custom `opencode-host` / `opencode-port` / `opencode-server-username` / `opencode-server-password` settings need no change — these still work as the fallback for the default `((default))` server entry.
Adds multi-connection support:
- One Emacs instance can connect to multiple OpenCode servers.
- Multiple connections can exist for the same project.
- "Current" connection outside of session and session-control buffers
 is derived based on project. In case of multiple connections for the
 same project the most-recently-used (by the user) one wins.
Configuration:
- Defcustom: opencode-servers, an alist of named server connections.
- Defcustom: opencode-find-server-function, a function that, if set,
 used to map projects to OpenCode servers.
- opencode-host, opencode-port, opencode-server-username and
 opencode-server-password now act as fallbacks for entries that omit
 the matching field. opencode-servers defaults to ((default)) (i.e.
 fall back on the scalar defcustoms for everything), so existing
 configurations continue to work.
Incompatible change: opencode-file-edited-functions,
opencode-files-finished-editing-functions and
opencode-project-files-finished-editing-functions now take a leading
"context" (effectively connection) argument.
cmm force-pushed multi-connection from 4346a84a11 to 2f416343e4 2026年07月01日 18:08:08 +02:00 Compare
Owner
Copy link

Interesting, I haven't fully looked at it, but a couple high level things:

  1. I don't think we need to wrap every API call in opencode--with-connection. Slime has slime-current-connection that selects the active connection based on slime-dispatching-connection (dynamically bound when handling events), slime-buffer-connection (a buffer local variable that can be set), and slime-default-connection (the global default). We could just have a similar opencode-current-connection function called inside the API code to determine which connection to use, and then also in the api macro, after getting the response from the api when executing the body, bind opencode-dispatching-connection so that the body continues executing with the same active connection. Then we also wouldn't need to pass the connection to all the hook functions, just bind opencode-dispatching-connection when running the hooks.
  2. I'm not sure of the design where connections are mapped to projects with opencode-find-server-function, it fits your use of multiple opencode instances, but another major usecase could be someone spawning multiple containers each with their own opencode instance to work on different things on the same project in parallel. I would probably copy more the design of slime with slime-list-connections, slime-cycle-connections, slime-connection-list-make-default, etc. It would still work for your use-case as you could set opencode-buffer-connection in .dir-locals.el for your project, to have some connection be the default for a given project.
Interesting, I haven't fully looked at it, but a couple high level things: 1. I don't think we need to wrap every API call in opencode--with-connection. Slime has `slime-current-connection` that selects the active connection based on `slime-dispatching-connection` (dynamically bound when handling events), `slime-buffer-connection` (a buffer local variable that can be set), and `slime-default-connection` (the global default). We could just have a similar `opencode-current-connection` function called inside the API code to determine which connection to use, and then also in the api macro, after getting the response from the api when executing the body, bind `opencode-dispatching-connection` so that the body continues executing with the same active connection. Then we also wouldn't need to pass the connection to all the hook functions, just bind `opencode-dispatching-connection` when running the hooks. 2. I'm not sure of the design where connections are mapped to projects with `opencode-find-server-function`, it fits your use of multiple opencode instances, but another major usecase could be someone spawning multiple containers each with their own opencode instance to work on different things on the same project in parallel. I would probably copy more the design of slime with `slime-list-connections`, `slime-cycle-connections`, `slime-connection-list-make-default`, etc. It would still work for your use-case as you could set `opencode-buffer-connection` in `.dir-locals.el` for your project, to have some connection be the default for a given project.
Author
First-time contributor
Copy link
  1. hmm. the dynamic-binding design is pretty much identical actually, except names are sort of shifted (and most with-connection uses around API calls can and will be eliminated). I'll harmonize.
  2. I don't see how hook functions can lose the connection argument (which I prudishly call "context" in that, uh, context), given that the (apparent) idea is:
  • third parties can add their own hooks and are not expected to know (too much) about opencode.el's inner mechanics, and
  • hooks can cause their own async work, where in the third-party case it is not necessarily opencode.el who fires the callbacks, so if you do not pass the hooks your context explicitly you wouldn't know how or where to preserve it for their callbacks.
    I may have totally misunderstood the design, of course!
  1. leaning on .dir-locals looks reasonable, should've thought of that. need to see how that would play with the multiple-connections-in-one-project/MRU thing though, because .dir-locals don't have to be in project roots etc. there's also the fact that I tend to commit .dir-locals (is that weird?), which makes them an awkward place to leak my personal setup into... but on the third paw, the DWIM server selection I want is probably just a matter of around-advicing opencode() anyway, so this part is NBD really
1. hmm. the dynamic-binding design is pretty much identical actually, except names are sort of shifted (and most `with-connection` uses around API calls can and will be eliminated). I'll harmonize. 2. I don't see how hook functions can lose the connection argument (which I prudishly call "context" in that, uh, context), given that the (apparent) idea is: - third parties can add their own hooks and are not expected to know (too much) about `opencode.el`'s inner mechanics, and - hooks can cause their own async work, where in the third-party case it is not necessarily `opencode.el` who fires the callbacks, so if you do not pass the hooks your context explicitly you wouldn't know how or where to preserve it for their callbacks. I may have totally misunderstood the design, of course! 3. leaning on `.dir-locals` looks reasonable, should've thought of that. need to see how that would play with the multiple-connections-in-one-project/MRU thing though, because `.dir-locals` don't have to be in project roots etc. there's also the fact that I tend to commit `.dir-locals` (is that weird?), which makes them an awkward place to leak my personal setup into... but on the third paw, the DWIM server selection I want is probably just a matter of around-advicing `opencode()` anyway, so this part is NBD really
This pull request has changes conflicting with the target branch.
  • opencode-sessions.el
View command line instructions

Manual merge helper

Use this merge commit message when completing the merge manually.

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u multi-connection:cmm-multi-connection
git switch cmm-multi-connection

Merge

Merge the changes and update on Forgejo.
git switch main
git merge --no-ff cmm-multi-connection
git switch cmm-multi-connection
git rebase main
git switch main
git merge --ff-only cmm-multi-connection
git switch cmm-multi-connection
git rebase main
git switch main
git merge --no-ff cmm-multi-connection
git switch main
git merge --squash cmm-multi-connection
git switch main
git merge --ff-only cmm-multi-connection
git switch main
git merge cmm-multi-connection
git push origin main
Sign in to join this conversation.
No reviewers
Labels
Clear labels
No items
No labels
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
sczi/opencode.el!35
Reference in a new issue
sczi/opencode.el
No description provided.
Delete branch "cmm/opencode.el:multi-connection"

Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?