Skip to content

Commit 4008866

Browse files
committed
refactor(core): remove core.lua and adopt services as shared primitives
test: split legacy core_spec into service-focused specs chore(docs): align handlers/services AGENTS boundaries
1 parent efb08e4 commit 4008866

25 files changed

Lines changed: 1704 additions & 1330 deletions

lua/opencode/commands/handlers/AGENTS.md

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,49 @@
11
# AGENTS.md (handlers)
22

3-
This directory owns domain actions and command definitions.
3+
This directory owns command-facing action adapters and command definitions.
4+
5+
One-line positioning:
6+
- `handlers/` = **command-entry adapters**
7+
- `services/` = **cross-entry reusable business primitives**
48

59
## Scope
610

7-
- `M.actions`: domain operations
11+
- `M.actions`: command-facing action adapters
812
- `M.command_defs`: command-facing definitions (desc/completions/execute)
913
- No command pipeline logic here
1014

15+
## Relation with services
16+
17+
- Handlers should call `services/*` when logic is shared by command/UI/quick_chat entries.
18+
- Handlers should not become the only place for reusable business logic.
19+
- If an action is needed outside command entry paths, move/keep it in `services/`.
20+
1121
## Hard Invariants
1222

1323
- Handlers do not call `dispatch.execute` directly
1424
- Handlers do not parse command text
1525
- Handlers do not decide hook routing
1626
- Keep action behavior identical across entry points
1727
- Handlers must not introduce any new bind/execute entry symbols (`*.run`, `bind_*`, or dispatch wrappers)
28+
- Prefer `services/*` over direct new `opencode.session` / `opencode.api` requires in handlers
1829

1930
## Structure Guidance
2031

2132
- Keep `actions` and `command_defs` aligned by domain
2233
- Keep keymap compatibility aliases explicit and grouped
2334
- Avoid duplicating command validation already guaranteed by parse schema
2435

36+
## Current boundary debt (file-level TODO)
37+
38+
The following handler files still directly require `opencode.session` and should be routed via services APIs.
39+
40+
- [ ] `lua/opencode/commands/handlers/diff.lua` -> `opencode.session`
41+
- [ ] `lua/opencode/commands/handlers/session.lua` -> `opencode.session`
42+
43+
Sync rule:
44+
- keep this list aligned with `lua/opencode/services/AGENTS.md`
45+
- remove an item only after code + tests pass
46+
2547
## Editing Rules
2648

2749
- Prefer consolidation over introducing new layers
@@ -46,12 +68,14 @@ This directory owns domain actions and command definitions.
4668
- Is `command_defs` declarative and minimal?
4769
- Are aliases grouped and obvious?
4870
- Did we avoid reintroducing duplicate argument validation paths?
71+
- Did we add any new direct `session/api` requires in handlers?
4972

5073
## Reject Conditions
5174

5275
- Any direct call to `dispatch.execute` from handlers
5376
- Any parse/hook routing logic added to handlers
5477
- Any new entry-style wrapper added in handlers
78+
- Any new direct `require('opencode.session')` or `require('opencode.api')` in handlers without exception note
5579

5680
## Minimal Regression Commands
5781

@@ -63,6 +87,6 @@ This directory owns domain actions and command definitions.
6387
## Entry Notes For New Agents
6488

6589
- Start from the domain file you are touching (`window/session/diff/workflow/surface/agent/permission`), then verify invariants against `commands/dispatch.lua`.
66-
- Treat handlers as **domain behavior + command definition only**.
90+
- Treat handlers as **command adaptation + command definition only**.
6791
- If you feel the need to touch parse/dispatch from handlers, stop and move that change to the command infrastructure layer.
6892
- Keep compatibility aliases explicit, local, and justified inline.

lua/opencode/commands/handlers/agent.lua

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
local core = require('opencode.core')
21
local config_file = require('opencode.config_file')
32
---@type OpencodeState
43
local state = require('opencode.state')
54
local util = require('opencode.util')
65
local Promise = require('opencode.promise')
6+
local agent_model = require('opencode.services.agent_model')
77

88
local M = {
99
actions = {},
@@ -18,23 +18,23 @@ local function invalid_arguments(message)
1818
end
1919

2020
function M.actions.configure_provider()
21-
core.configure_provider()
21+
agent_model.configure_provider()
2222
end
2323

2424
function M.actions.configure_variant()
25-
core.configure_variant()
25+
agent_model.configure_variant()
2626
end
2727

2828
function M.actions.cycle_variant()
29-
core.cycle_variant()
29+
agent_model.cycle_variant()
3030
end
3131

3232
function M.actions.agent_plan()
33-
core.switch_to_mode('plan')
33+
agent_model.switch_to_mode('plan')
3434
end
3535

3636
function M.actions.agent_build()
37-
core.switch_to_mode('build')
37+
agent_model.switch_to_mode('build')
3838
end
3939

4040
M.actions.select_agent = Promise.async(function()
@@ -47,7 +47,7 @@ M.actions.select_agent = Promise.async(function()
4747
return
4848
end
4949

50-
core.switch_to_mode(selection)
50+
agent_model.switch_to_mode(selection)
5151
end)
5252
end)
5353

@@ -60,11 +60,11 @@ M.actions.switch_mode = Promise.async(function()
6060
end
6161

6262
local next_index = (current_index % #modes) + 1
63-
core.switch_to_mode(modes[next_index])
63+
agent_model.switch_to_mode(modes[next_index])
6464
end)
6565

6666
M.actions.current_model = Promise.async(function()
67-
return core.initialize_current_model()
67+
return agent_model.initialize_current_model()
6868
end)
6969

7070
local agent_subcommands = { 'plan', 'build', 'select' }

lua/opencode/commands/handlers/diff.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
local core = require('opencode.core')
21
local git_review = require('opencode.git_review')
32
local session_store = require('opencode.session')
43
---@type OpencodeState
54
local state = require('opencode.state')
5+
local session_runtime = require('opencode.services.session_runtime')
66

77
local M = {
88
actions = {},
@@ -11,7 +11,7 @@ local M = {
1111
local diff_subcommands = { 'open', 'next', 'prev', 'close' }
1212

1313
local function with_output_open(callback, open_if_closed)
14-
local open_fn = open_if_closed and core.open_if_closed or core.open
14+
local open_fn = open_if_closed and session_runtime.open_if_closed or session_runtime.open
1515
return function(...)
1616
local args = { ... }
1717
open_fn({ new_session = false, focus = 'output' }):and_then(function()

lua/opencode/commands/handlers/session.lua

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
local core = require('opencode.core')
21
---@type OpencodeState
32
local state = require('opencode.state')
43
local session_store = require('opencode.session')
54
local Promise = require('opencode.promise')
65
local window_actions = require('opencode.commands.handlers.window').actions
6+
local session_runtime = require('opencode.services.session_runtime')
7+
local agent_model = require('opencode.services.agent_model')
78

89
local M = {
910
actions = {},
@@ -77,13 +78,13 @@ local function run_api_action_with_checktime(request_promise, error_prefix)
7778
end
7879

7980
function M.actions.open_input_new_session()
80-
return core.open({ new_session = true, focus = 'input', start_insert = true })
81+
return session_runtime.open({ new_session = true, focus = 'input', start_insert = true })
8182
end
8283

8384
---@param title string
8485
function M.actions.open_input_new_session_with_title(title)
8586
return Promise.async(function(session_title)
86-
local new_session = core.create_new_session(session_title):await()
87+
local new_session = session_runtime.create_new_session(session_title):await()
8788
if not new_session then
8889
vim.notify('Failed to create new session', vim.log.levels.ERROR)
8990
return
@@ -96,12 +97,12 @@ end
9697

9798
---@param parent_id? string
9899
function M.actions.select_session(parent_id)
99-
core.select_session(parent_id)
100+
session_runtime.select_session(parent_id)
100101
end
101102

102103
function M.actions.select_child_session()
103104
local active = state.active_session
104-
core.select_session(active and active.id or nil)
105+
session_runtime.select_session(active and active.id or nil)
105106
end
106107

107108
---@param current_session? Session
@@ -158,19 +159,18 @@ function M.actions.unshare()
158159
end)
159160
end
160161

161-
function M.actions.initialize()
162-
return Promise.async(function()
163-
local id = require('opencode.id')
164-
local state_obj = state
165-
local core_obj = core
162+
function M.actions.initialize()
163+
return Promise.async(function()
164+
local id = require('opencode.id')
165+
local state_obj = state
166166

167-
local new_session = core_obj.create_new_session('AGENTS.md Initialization'):await()
167+
local new_session = session_runtime.create_new_session('AGENTS.md Initialization'):await()
168168
if not new_session then
169169
vim.notify('Failed to create new session', vim.log.levels.ERROR)
170170
return
171171
end
172172

173-
if not core_obj.initialize_current_model():await() or not state_obj.current_model then
173+
if not agent_model.initialize_current_model():await() or not state_obj.current_model then
174174
vim.notify('No model selected', vim.log.levels.ERROR)
175175
return
176176
end
@@ -369,7 +369,7 @@ function M.actions.fork_session(message_id)
369369
vim.schedule(function()
370370
if response and response.id then
371371
vim.notify('Session forked successfully. New session ID: ' .. response.id, vim.log.levels.INFO)
372-
core.switch_session(response.id)
372+
session_runtime.switch_session(response.id)
373373
else
374374
vim.notify('Session forked but no new session ID received', vim.log.levels.WARN)
375375
end

lua/opencode/commands/handlers/window.lua

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
local core = require('opencode.core')
21
---@type OpencodeState
32
local state = require('opencode.state')
43
local ui = require('opencode.ui.ui')
54
local config = require('opencode.config')
65
local Promise = require('opencode.promise')
76
local input_window = require('opencode.ui.input_window')
7+
local session_runtime = require('opencode.services.session_runtime')
88

99
local M = {
1010
actions = {},
@@ -19,11 +19,11 @@ local function invalid_arguments(message)
1919
end
2020

2121
function M.actions.open_input()
22-
return core.open({ new_session = false, focus = 'input', start_insert = true })
22+
return session_runtime.open({ new_session = false, focus = 'input', start_insert = true })
2323
end
2424

2525
function M.actions.open_output()
26-
return core.open({ new_session = false, focus = 'output' })
26+
return session_runtime.open({ new_session = false, focus = 'output' })
2727
end
2828

2929
function M.actions.close()
@@ -47,7 +47,7 @@ function M.actions.get_window_state()
4747
end
4848

4949
function M.actions.cancel()
50-
core.cancel()
50+
session_runtime.cancel()
5151
end
5252

5353
---@param hidden OpencodeHiddenBuffers|nil
@@ -90,8 +90,8 @@ M.actions.toggle = Promise.async(function(new_session)
9090

9191
local function open_windows(restore_hidden)
9292
local ctx = build_toggle_open_context(restore_hidden == true)
93-
return core
94-
.open({
93+
return session_runtime
94+
.open({
9595
new_session = is_new_session,
9696
focus = ctx.focus,
9797
start_insert = false,
@@ -132,7 +132,7 @@ end)
132132
function M.actions.toggle_focus(new_session)
133133
if not ui.is_opencode_focused() then
134134
local focus = state.last_focused_opencode_window or 'input' ---@cast focus 'input' | 'output'
135-
core.open({ new_session = new_session == true, focus = focus })
135+
session_runtime.open({ new_session = new_session == true, focus = focus })
136136
else
137137
ui.return_to_last_code_win()
138138
end

lua/opencode/commands/handlers/workflow.lua

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
local core = require('opencode.core')
21
local util = require('opencode.util')
32
local config_file = require('opencode.config_file')
43
---@type OpencodeState
@@ -11,6 +10,7 @@ local Promise = require('opencode.promise')
1110
local input_window = require('opencode.ui.input_window')
1211
local ui = require('opencode.ui.ui')
1312
local nvim = vim['api']
13+
local session_runtime = require('opencode.services.session_runtime')
1414

1515
local M = {
1616
actions = {},
@@ -49,8 +49,8 @@ end
4949
---@param prompt string
5050
---@param opts SendMessageOpts
5151
local function run_with_opts(prompt, opts)
52-
return core.open(opts):and_then(function()
53-
return core.send_message(prompt, opts)
52+
return session_runtime.open(opts):and_then(function()
53+
return require('opencode.services.messaging').send_message(prompt, opts)
5454
end)
5555
end
5656

@@ -189,7 +189,7 @@ for _, action_name in ipairs({ 'debug_output', 'debug_message', 'debug_session'
189189
end
190190

191191
function M.actions.paste_image()
192-
core.paste_image_from_clipboard()
192+
session_runtime.paste_image_from_clipboard()
193193
end
194194

195195
M.actions.submit_input_prompt = Promise.async(function()
@@ -301,12 +301,12 @@ function M.actions.toggle_max_messages()
301301
end
302302

303303
M.actions.review = Promise.async(function(args)
304-
local new_session = core.create_new_session('Code review checklist for diffs and PRs'):await()
304+
local new_session = session_runtime.create_new_session('Code review checklist for diffs and PRs'):await()
305305
if not new_session then
306306
vim.notify('Failed to create new session', vim.log.levels.ERROR)
307307
return
308308
end
309-
if not core.initialize_current_model():await() or not state.current_model then
309+
if not require('opencode.services.agent_model').initialize_current_model():await() or not state.current_model then
310310
vim.notify('No model selected', vim.log.levels.ERROR)
311311
return
312312
end

0 commit comments

Comments
 (0)