Skip to content

Commit e6e31d0

Browse files
authored
Merge pull request #51 from editor-code-assistant/renew-chat-id
Do not re-use the same `chatId` forever
2 parents 80e3be4 + 52f8181 commit e6e31d0

File tree

4 files changed

+129
-1
lines changed

4 files changed

+129
-1
lines changed

lua/eca/mediator.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,8 @@ function mediator:mcps()
8686
return mcps
8787
end
8888

89+
function mediator:id()
90+
return self.state and self.state.id
91+
end
92+
8993
return mediator

lua/eca/sidebar.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1275,7 +1275,7 @@ function M:_send_message(message)
12751275

12761276
local contexts = self:get_contexts()
12771277
self.mediator:send("chat/prompt", {
1278-
chatId = self.id,
1278+
chatId = self.mediator:id(),
12791279
requestId = tostring(os.time()),
12801280
message = message,
12811281
contexts = contexts or {},

lua/eca/state.lua

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
---@field status string
1818
---
1919
---@class eca.State
20+
---@field id string?
2021
---@field status eca.StateStatus
2122
---@field config eca.StateConfig
2223
---@field usage eca.StateUsage
@@ -26,6 +27,7 @@ local State = {}
2627
---@return eca.State
2728
function State._new()
2829
local instance = setmetatable({
30+
id = nil,
2931
status = {
3032
state = "idle",
3133
text = "Idle",
@@ -99,6 +101,10 @@ function State:_chat_content_received(message)
99101
return
100102
end
101103

104+
if message.params.chatId and message.params.chatId ~= self.id then
105+
self.id = message.params.chatId
106+
end
107+
102108
local content = message.params.content
103109

104110
if content.type == "progress" then

tests/test_chat_id.lua

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
local MiniTest = require("mini.test")
2+
local eq = MiniTest.expect.equality
3+
local child = MiniTest.new_child_neovim()
4+
5+
local function flush(ms)
6+
vim.uv.sleep(ms or 50)
7+
child.api.nvim_eval("1")
8+
end
9+
10+
local function setup_env()
11+
-- Initialize everything
12+
_G.Server = require('eca.server').new()
13+
_G.State = require('eca.state').new()
14+
_G.Mediator = require('eca.mediator').new(_G.Server, _G.State)
15+
_G.Sidebar = require('eca.sidebar').new(1, _G.Mediator)
16+
end
17+
18+
local T = MiniTest.new_set({
19+
hooks = {
20+
pre_case = function()
21+
child.restart({ "-u", "scripts/minimal_init.lua" })
22+
child.lua([[require('eca.commands').setup()]])
23+
child.lua_func(setup_env)
24+
end,
25+
post_case = function()
26+
child.lua("if _G.Server and _G.Server.process then _G.Server.process:kill() end")
27+
end,
28+
post_once = child.stop,
29+
},
30+
})
31+
32+
T["chat id lifecycle"] = MiniTest.new_set()
33+
34+
T["chat id lifecycle"]["mediator id follows state id updates"] = function()
35+
-- Initially there is no chat id
36+
eq(child.lua_get("_G.State.id"), vim.NIL)
37+
eq(child.lua_get("_G.Mediator:id()"), vim.NIL)
38+
39+
-- Simulate a content notification with a chatId
40+
child.lua([[require('eca.observer').notify({
41+
method = 'chat/contentReceived',
42+
params = { chatId = 'chat-123', content = { type = 'progress', state = 'responding', text = '...' } },
43+
})]])
44+
flush()
45+
46+
eq(child.lua_get("_G.State.id"), "chat-123")
47+
eq(child.lua_get("_G.Mediator:id()"), "chat-123")
48+
end
49+
50+
T["chat id lifecycle"]["send request uses current chat id when available"] = function()
51+
-- Ensure no chat id initially
52+
eq(child.lua_get("_G.State.id"), vim.NIL)
53+
54+
-- Send a message before any chat id exists
55+
child.lua([[_G.Sidebar:_send_message('hello')]])
56+
57+
-- Inspect the last message sent to the server
58+
local last_json = child.lua_get("_G.Server.messages[#_G.Server.messages].content")
59+
local parsed = child.lua_get("vim.json.decode(...)", { last_json })
60+
eq(parsed.method, "chat/prompt")
61+
-- When id is nil, chatId should be absent in params
62+
eq(parsed.params.chatId == nil, true)
63+
64+
-- Now simulate receiving a new chat id
65+
child.lua([[require('eca.observer').notify({
66+
method = 'chat/contentReceived',
67+
params = { chatId = 'chat-new', content = { type = 'progress', state = 'responding', text = '...' } },
68+
})]])
69+
flush()
70+
eq(child.lua_get("_G.State.id"), "chat-new")
71+
72+
-- Send another message; it should include the new chat id
73+
child.lua([[_G.Sidebar:_send_message('again')]])
74+
local last_json2 = child.lua_get("_G.Server.messages[#_G.Server.messages].content")
75+
local parsed2 = child.lua_get("vim.json.decode(...)", { last_json2 })
76+
eq(parsed2.method, "chat/prompt")
77+
eq(parsed2.params.chatId, "chat-new")
78+
end
79+
80+
T["chat id lifecycle"]["reopening neovim uses a new chat id"] = function()
81+
-- First session: set a chat id via notification
82+
child.lua([[require('eca.observer').notify({
83+
method = 'chat/contentReceived',
84+
params = { chatId = 'chat-first', content = { type = 'progress', state = 'responding', text = '...' } },
85+
})]])
86+
flush()
87+
eq(child.lua_get("_G.State.id"), "chat-first")
88+
89+
-- Restart Neovim to simulate reopening
90+
child.restart({ "-u", "scripts/minimal_init.lua" })
91+
child.lua([[require('eca.commands').setup()]])
92+
child.lua_func(setup_env)
93+
94+
-- New session should not carry over the previous chat id
95+
eq(child.lua_get("_G.State.id"), vim.NIL)
96+
97+
-- First message should not include a chatId
98+
child.lua([[_G.Sidebar:_send_message('hello after reopen')]])
99+
local last_json3 = child.lua_get("_G.Server.messages[#_G.Server.messages].content")
100+
local parsed = child.lua_get("vim.json.decode(...)", { last_json3 })
101+
eq(parsed.method, "chat/prompt")
102+
eq(parsed.params.chatId == nil, true)
103+
104+
-- After receiving content, a new chat id should be used
105+
child.lua([[require('eca.observer').notify({
106+
method = 'chat/contentReceived',
107+
params = { chatId = 'chat-second', content = { type = 'progress', state = 'responding', text = '...' } },
108+
})]])
109+
flush()
110+
eq(child.lua_get("_G.State.id"), "chat-second")
111+
112+
child.lua([[_G.Sidebar:_send_message('use new id')]])
113+
local last_json4 = child.lua_get("_G.Server.messages[#_G.Server.messages].content")
114+
local parsed2 = child.lua_get("vim.json.decode(...)", { last_json4 })
115+
eq(parsed2.params.chatId, "chat-second")
116+
end
117+
118+
return T

0 commit comments

Comments
 (0)