refactor(commands): split api into parse/dispatch/handler boundaries#337
Conversation
|
My modifications here were a bit rushed, but I need to get some sleep (and unfortunately, I had to submit another large PR...). @sudo-tee |
|
Thanks for the PR. I will have a look |
There was a problem hiding this comment.
Pull request overview
Refactors Opencode’s command execution into an explicit pipeline (entry -> router -> parse -> dispatch -> handlers) and introduces an extension-backed registry so command/slash definitions and handlers are centrally defined and validated.
Changes:
- Added a registry + loader layer for builtin and user extensions (commands, slash commands, handlers, hooks, etc.) with conflict policies.
- Implemented the new command stack (router/parse/dispatch/handlers) and updated entry points (Neovim
:Opencode, slash commands, keymaps) to route through it. - Slimmed
api.luainto a public “actions surface” that composes handler action modules, and added extensive unit coverage/guards for dependency boundaries and error normalization.
Reviewed changes
Copilot reviewed 32 out of 32 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/registry_loader_spec.lua | Validates extension loading, isolation, conflict policy, and legacy usecases adaptation. |
| tests/unit/keymap_spec.lua | Updates keymap tests for router-based dispatch and legacy alias behavior. |
| tests/unit/entry_dependency_guard_spec.lua | Adds dependency boundary guards (router boundary, no dynamic api[...] access). |
| tests/unit/commands_router_spec.lua | Covers API-method-to-command argv routing and invalid_command behavior. |
| tests/unit/commands_parse_spec.lua | Covers parse results and stable error shapes (unknown/missing/invalid subcommand). |
| tests/unit/commands_handlers_spec.lua | Validates handler isolation, duplicate detection, and error normalization via executor. |
| tests/unit/commands_dispatch_spec.lua | Covers lifecycle ordering, hook isolation, registry hook pipeline, and error normalization. |
| tests/unit/api_spec.lua | Updates expectations for registry-backed command defs and public API boundary. |
| lua/opencode/types.lua | Adds/updates types for command registry, lifecycle hooks, dispatch results, and extension config. |
| lua/opencode/state/init.lua | Updates state type annotations to include common top-level fields. |
| lua/opencode/registry/loader.lua | Adds extension loader with builtin/user extensions and legacy usecases warning/adaptation. |
| lua/opencode/registry/init.lua | Implements registry capabilities, conflict policy handling, and lazy setup. |
| lua/opencode/registry/extensions/slash.lua | Provides builtin slash-command specs mapped to API methods. |
| lua/opencode/registry/extensions/commands.lua | Exposes builtin command definitions via builtin_commands.get(). |
| lua/opencode/registry/builtin_commands.lua | Defines builtin command schema (handler_id, completions, nested validation, etc.). |
| lua/opencode/keymap.lua | Refactors keymap binding to router + explicit non-command whitelist callbacks and legacy aliases. |
| lua/opencode/init.lua | Initializes registry and registers :Opencode via opencode.commands. |
| lua/opencode/config.lua | Adds extensions configuration defaults (builtin toggles + conflict policy). |
| lua/opencode/commands/slash.lua | Builds runtime slash commands from registry + user commands, routes via router. |
| lua/opencode/commands/router.lua | Routes entry invocations to parse+dispatch, provides API-method-to-command argv mapping. |
| lua/opencode/commands/parse.lua | Parses argv/range into intent and returns stable parse errors. |
| lua/opencode/commands/init.lua | Registers :Opencode and provides completion via registry + completion providers. |
| lua/opencode/commands/handlers/*.lua | Implements command semantics in handler modules (window/session/diff/workflow/agent/permission). |
| lua/opencode/commands/handlers.lua | Central handler executor, duplicate detection, and extension handler integration. |
| lua/opencode/commands/dispatch.lua | Dispatch lifecycle (before/after/error/finally), hook pipeline, normalized errors. |
| lua/opencode/commands/completion_providers.lua | Adds completion provider registry (e.g., user_commands). |
| lua/opencode/api.lua | Replaces monolithic API with composed action tables from handler modules. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
registry part should remove in this pr |
|
I was wondering. If the plan is to move to a registry. The core should also use it all the way. I agree that this could be a seperate PR |
|
That was my local exploratory code, and I still believe some kind of registration mechanism is necessary. It would unify built-in features with those added in the future via recipes or plugins, but it isn't a problem with an immediately obvious, elegant design. I initially wanted to find the answer to this before starting changes on api.lua, but I failed. The time might not be right to solve this problem yet (or perhaps my expectations for it were too high). |
|
No worries, take your time, there is no rush |
2ffec73 to
6f85c4b
Compare
- extract command flow into parse, dispatch, complete, and slash modules - move domain behaviors into handlers (window, workflow, session, diff, surface, agent, permission) - standardize command_defs around execute/completions and add duplicate-definition fail-fast checks - route keymap/slash entry points through the same command boundary - refresh command-layer tests to lock parse/dispatch/handler contracts
6f85c4b to
c885db1
Compare
|
Thanks for the super quick review — I honestly didn’t expect feedback to come in this fast. @sudo-tee |
|
I haven’t checked in here for a while—how are things going? By the way, do you use GitHub Copilot? I was wondering if you frequently encounter compression issues in OpenCode. Does it bother you? @sudo-tee -- Sorry, my email notifications often don't arrive on time. I'll try that branch right now. |
|
I have been more busy the last weeks, and so submerge in the renderer refactor that I totally forgot about this MR. I do use copilot and I do have frequent compaction on smaller models hovever on sonnet 4.6 and gpt.5.4 it ok, it does not compact as often. For this PR specifically I will try to do some testing, to get it merged, it's a nice split up. for future work. |
|
No rush on this PR, After the render refactor, a few days still needed of observation @sudo-tee I'm just giving you a few small suggestions for that PR, wait a moment |
|
I believe the splitting of this api.lua is strongly related to the design issue of "Core isn't feature-agnostic." I have already identified some problems in the current design that need further adjustments. I will revise this PR later and may further explain the direction for subsequent refinement. |
- Converge execution flow to parse/build -> bind_action_context -> dispatch.execute - Keep parse pure by returning ParsedIntent without execute injection - Route API/keymap/slash through build_parsed_intent + execute_parsed_intent - Add runtime dispatch hook register/unregister with command filters - Fix keymap table-arg mutation leak and normalize invalid permission subcommands - Update type/event annotations and add axis/mapping regression tests
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 32 out of 32 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- include allowed subcommands in agent/session/diff invalid argument errors - clamp help table column width in narrow windows to avoid format failures - fail run_tests.sh on non-zero nvim/plenary exits in addition to output parsing - add handlers tests for actionable subcommand errors and narrow-window help rendering
|
The current PR has been developed and is ready for you to try out. @sudo-tee I have a relatively complete roadmap, and I will discuss it with you elsewhere later. Thank you for your time and effort Actually, the next step is to gradually disassemble core.lua and migrate it to individual modules (you'll notice there are many forwarding operations to core.lua in the handler). I believe we don't actually need core.lua -- we just need to distinguish between capabilities that require communication with the opencode server to obtain, and the parts where opencode.nvim has the opportunity to showcase its unique strengths.
|
|
@jensenojs Yes I agree with this. Your pr is a really good start in that direction. I will do a last review/test and we can merge this |
Summary
for #322
The main change here is breaking up the command logic that was piled into
api.lua.The execution flow is now split into parse / dispatch / complete / slash layers, with domain behaviors moved into handlers (window/workflow/session/diff/surface/agent/permission). Makes it easier to find and change command behavior without digging through one big file.
Keymap string actions and
:Opencode ...now go through the same routing boundary, so the two entry points can't drift independently. Also added a duplicate key fail-fast when aggregating command definitions.The dispatch layer has
on_command_before/after/error/finallyhooks wired up but not yet exposed — no config defaults, no subscribers. It's a placeholder for a future event unification pass, not meant to be used yet.