Skip to content

Commit 320e7ef

Browse files
committed
feat(timeline-picker): add fork session action
1 parent b885ccc commit 320e7ef

File tree

4 files changed

+64
-15
lines changed

4 files changed

+64
-15
lines changed

lua/opencode/api.lua

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -683,7 +683,9 @@ end
683683
function M.timeline()
684684
local user_messages = {}
685685
for _, msg in ipairs(state.messages or {}) do
686-
if msg.info.role == 'user' then
686+
local parts = msg.parts or {}
687+
local is_summary = #parts == 1 and parts[1].synthetic == true
688+
if msg.info.role == 'user' and not is_summary then
687689
table.insert(user_messages, msg)
688690
end
689691
end
@@ -700,6 +702,39 @@ function M.timeline()
700702
end)
701703
end
702704

705+
function M.fork_session(message_id)
706+
if not state.active_session then
707+
vim.notify('No active session to fork', vim.log.levels.WARN)
708+
return
709+
end
710+
711+
local message_to_fork = message_id or state.last_user_message and state.last_user_message.info.id
712+
if not message_to_fork then
713+
vim.notify('No user message to fork from', vim.log.levels.WARN)
714+
return
715+
end
716+
717+
state.api_client
718+
:fork_session(state.active_session.id, {
719+
messageID = message_to_fork,
720+
})
721+
:and_then(function(response)
722+
vim.schedule(function()
723+
if response and response.id then
724+
vim.notify('Session forked successfully. New session ID: ' .. response.id, vim.log.levels.INFO)
725+
core.switch_session(response.id)
726+
else
727+
vim.notify('Session forked but no new session ID received', vim.log.levels.WARN)
728+
end
729+
end)
730+
end)
731+
:catch(function(err)
732+
vim.schedule(function()
733+
vim.notify('Failed to fork session: ' .. vim.inspect(err), vim.log.levels.ERROR)
734+
end)
735+
end)
736+
end
737+
703738
-- Returns the ID of the next user message after the current undo point
704739
-- This is a port of the opencode tui logic
705740
-- https://github.com/sst/opencode/blob/dev/packages/tui/internal/components/chat/messages.go#L1199

lua/opencode/api_client.lua

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,15 @@ function OpencodeApiClient:summarize_session(id, summary_data, directory)
213213
return self:_call('/session/' .. id .. '/summarize', 'POST', summary_data, { directory = directory })
214214
end
215215

216+
--- Fork an existing session at a specific message
217+
--- @param id string Session ID (required)
218+
--- @param fork_data {messageID?: string}|nil Fork data
219+
--- @param directory string|nil Directory path
220+
--- @return Promise<Session>
221+
function OpencodeApiClient:fork_session(id, fork_data, directory)
222+
return self:_call('/session/' .. id .. '/fork', 'POST', fork_data, { directory = directory })
223+
end
224+
216225
-- Message endpoints
217226

218227
--- List messages for a session

lua/opencode/core.lua

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,26 @@ function M.select_session(parent_id)
2525
end
2626
return
2727
end
28-
-- clear the model so it can be set by the session. If it doesn't get set
29-
-- then core.get_model() will reset it to the default
30-
state.current_model = nil
31-
state.active_session = selected_session
32-
if state.windows then
33-
state.restore_points = {}
34-
-- Don't need to update either renderer because they subscribe to
35-
-- session changes
36-
ui.focus_input()
37-
else
38-
M.open()
39-
end
28+
M.switch_session(selected_session.id)
4029
end)
4130
end
4231

32+
function M.switch_session(session_id)
33+
local selected_session = session.get_by_id(session_id)
34+
-- clear the model so it can be set by the session. If it doesn't get set
35+
-- then core.get_model() will reset it to the default
36+
state.current_model = nil
37+
state.active_session = selected_session
38+
if state.windows then
39+
state.restore_points = {}
40+
-- Don't need to update either renderer because they subscribe to
41+
-- session changes
42+
ui.focus_input()
43+
else
44+
M.open()
45+
end
46+
end
47+
4348
---@param opts? OpenOpts
4449
function M.open(opts)
4550
opts = opts or { focus = 'input', new_session = false }

lua/opencode/ui/timeline_picker.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
local M = {}
22
local picker = require('opencode.ui.picker')
33
local config = require('lua.opencode.config')
4+
local api = require('opencode.api')
45

56
local picker_title = function()
67
local config = require('opencode.config') --[[@as OpencodeConfig]]
@@ -329,8 +330,7 @@ function M.pick(messages, callback)
329330
end
330331

331332
local function on_fork(msg)
332-
-- TODO: Implement fork functionality
333-
vim.notify('Fork functionality not yet implemented', vim.log.levels.WARN)
333+
api.fork_session(msg.info.id)
334334
end
335335

336336
vim.schedule(function()

0 commit comments

Comments
 (0)