Skip to content

Commit 9c90bad

Browse files
authored
Merge pull request #47 from editor-code-assistant/select-model-and-behavior
Select model and behavior
2 parents 8ae1e0a + 9b3d6bd commit 9c90bad

File tree

10 files changed

+347
-7
lines changed

10 files changed

+347
-7
lines changed

lua/eca/commands.lua

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,50 @@ function M.setup()
343343
desc = "Emergency fix for treesitter issues in ECA chat",
344344
})
345345

346+
vim.api.nvim_create_user_command("EcaChatSelectModel", function()
347+
local eca = require("eca")
348+
349+
if not eca or not eca.current or not eca.current.sidebar then
350+
Logger.notify("No active ECA sidebar found", vim.log.levels.WARN)
351+
return
352+
end
353+
354+
local chat = eca.current.sidebar
355+
local models = chat.mediator:models()
356+
357+
vim.ui.select(models, {
358+
prompt = "Select ECA Chat Model:",
359+
}, function(choice)
360+
if choice then
361+
chat.mediator:update_selected_model(choice)
362+
end
363+
end)
364+
end, {
365+
desc = "Select current ECA Chat model",
366+
})
367+
368+
vim.api.nvim_create_user_command("EcaChatSelectBehavior", function()
369+
local eca = require("eca")
370+
371+
if not eca or not eca.current or not eca.current.sidebar then
372+
Logger.notify("No active ECA sidebar found", vim.log.levels.WARN)
373+
return
374+
end
375+
376+
local chat = eca.current.sidebar
377+
local behaviors = chat.mediator:behaviors()
378+
379+
vim.ui.select(behaviors, {
380+
prompt = "Select ECA Chat Behavior:",
381+
}, function(choice)
382+
if choice then
383+
chat.mediator:update_selected_behavior(choice)
384+
end
385+
end)
386+
end, {
387+
desc = "Select current ECA Chat behavior",
388+
})
389+
346390
Logger.debug("ECA commands registered")
347391
end
348392

lua/eca/mediator.lua

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,30 @@ function mediator:send(method, params, callback)
2626
self.server:send_request(method, params, callback)
2727
end
2828

29+
function mediator:behaviors()
30+
return self.state.config.behaviors.list
31+
end
32+
2933
function mediator:selected_behavior()
3034
return self.state.config.behaviors.selected
3135
end
3236

37+
function mediator:update_selected_behavior(behavior)
38+
self.state:update_selected_behavior(behavior)
39+
end
40+
41+
function mediator:models()
42+
return self.state.config.models.list
43+
end
44+
3345
function mediator:selected_model()
3446
return self.state.config.models.selected
3547
end
3648

49+
function mediator:update_selected_model(model)
50+
self.state:update_selected_model(model)
51+
end
52+
3753
function mediator:tokens_session()
3854
return self.state.usage.tokens.session
3955
end

lua/eca/server.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ end
9898
---in testing
9999
---@param opts? eca.ServerStartOpts
100100
function M:start(opts)
101-
opts = opts or { initialize = true }
101+
opts = vim.tbl_deep_extend("force", { initialize = true }, opts or {})
102102

103103
local this_file = debug.getinfo(1, "S").source:sub(2)
104104
local proj_root = vim.fn.fnamemodify(this_file, ":p:h:h:h")
@@ -112,7 +112,7 @@ function M:start(opts)
112112

113113
local lua_cmd = string.format("lua ServerPath.run(%s)", Utils.lua_quote(Config.server_path or ""))
114114

115-
local cmd = { nvim_exe, "--headless", "--noplugin", "-u", script_path, "-c", lua_cmd }
115+
local cmd = { nvim_exe, "--headless", "--noplugin", (opts.clean and " --clean" or ""), "-u", script_path, "-c", lua_cmd }
116116

117117
vim.system(cmd, { text = true }, function(out)
118118
if out.code ~= 0 then

lua/eca/sidebar.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1279,6 +1279,8 @@ function M:_send_message(message)
12791279
requestId = tostring(os.time()),
12801280
message = message,
12811281
contexts = contexts or {},
1282+
model = self.mediator:selected_model(),
1283+
behavior = self.mediator:selected_behavior(),
12821284
}, function(err, result)
12831285
if err then
12841286
print("err is " .. err)

lua/eca/state.lua

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ end
168168
function State:_update_usage(usage)
169169
self.usage = {
170170
tokens = {
171-
limit = (usage.limit and usage.limit.output) or self.usage.tokens.limit,
171+
limit = (usage.limit and usage.limit.context) or self.usage.tokens.limit,
172172
session = usage.sessionTokens or self.usage.tokens.session,
173173
},
174174
costs = {
@@ -198,4 +198,28 @@ function State:_update_tools(tool)
198198
end)
199199
end
200200

201+
function State:update_selected_model(model)
202+
if not model or type(model) ~= "string" then
203+
return
204+
end
205+
206+
self.config.models.selected = model
207+
208+
vim.schedule(function()
209+
require("eca.observer").notify({ type = "state/updated", content = { config = vim.deepcopy(self.config) } })
210+
end)
211+
end
212+
213+
function State:update_selected_behavior(behavior)
214+
if not behavior or type(behavior) ~= "string" then
215+
return
216+
end
217+
218+
self.config.behaviors.selected = behavior
219+
220+
vim.schedule(function()
221+
require("eca.observer").notify({ type = "state/updated", content = { config = vim.deepcopy(self.config) } })
222+
end)
223+
end
224+
201225
return State

scripts/server_path.lua

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,19 @@ local ServerPath = {}
33
-- Setup if headless
44
if #vim.api.nvim_list_uis() == 0 then
55
_G.ServerPath = ServerPath
6+
7+
-- hijack to make server tests work on CI using --clean mode
8+
local eca_available = pcall(require, "eca")
9+
if not eca_available then
10+
vim.cmd([[let &rtp.=','.getcwd()]])
11+
vim.cmd('set rtp+=deps/nui.nvim')
12+
vim.cmd('set rtp+=deps/eca-nvim')
13+
end
14+
615
vim.o.swapfile = false
716
vim.o.backup = false
817
vim.o.writebackup = false
18+
919
require("eca").setup({})
1020
end
1121

tests/test_select_commands.lua

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
local MiniTest = require("mini.test")
2+
local eq = MiniTest.expect.equality
3+
local child = MiniTest.new_child_neovim()
4+
5+
local function setup_test_environment()
6+
-- Setup commands
7+
require('eca.commands').setup()
8+
9+
-- Initialize everything
10+
_G.Server = require('eca.server').new()
11+
_G.State = require('eca.state').new()
12+
_G.Mediator = require('eca.mediator').new(_G.Server, _G.State)
13+
_G.Sidebar = require('eca.sidebar').new(1, _G.Mediator)
14+
_G.Eca = require('eca')
15+
_G.Eca.current = { sidebar = _G.Sidebar }
16+
17+
-- Mock vim.ui.select for testing
18+
_G.selected_choice = nil
19+
_G.shown_items = nil
20+
_G.shown_prompt = nil
21+
_G.original_select = vim.ui.select
22+
23+
_G.mock_select = function(choice)
24+
_G.selected_choice = choice
25+
vim.ui.select = function(items, opts, on_choice)
26+
_G.shown_items = items
27+
_G.shown_prompt = opts.prompt
28+
on_choice(choice)
29+
end
30+
end
31+
32+
_G.restore_select = function()
33+
vim.ui.select = _G.original_select
34+
end
35+
end
36+
37+
local T = MiniTest.new_set({
38+
hooks = {
39+
pre_case = function()
40+
child.restart({ "-u", "scripts/minimal_init.lua" })
41+
child.lua_func(setup_test_environment)
42+
end,
43+
post_case = function()
44+
child.lua([[_G.restore_select()]])
45+
end,
46+
post_once = child.stop,
47+
},
48+
})
49+
50+
-- Test EcaChatSelectModel command
51+
T["EcaChatSelectModel"] = MiniTest.new_set()
52+
53+
T["EcaChatSelectModel"]["command is registered"] = function()
54+
local commands = child.lua_get("vim.api.nvim_get_commands({})")
55+
eq(type(commands.EcaChatSelectModel), "table")
56+
eq(commands.EcaChatSelectModel.name, "EcaChatSelectModel")
57+
end
58+
59+
T["EcaChatSelectModel"]["updates state when model selected"] = function()
60+
-- Setup initial state with models
61+
child.lua([[
62+
_G.State.config.models.list = { "model1", "model2", "model3" }
63+
_G.State.config.models.selected = "model1"
64+
65+
-- Mock vim.ui.select to auto-select model2
66+
_G.mock_select("model2")
67+
]])
68+
69+
-- Execute command
70+
child.cmd("EcaChatSelectModel")
71+
72+
-- Check that state was updated
73+
eq(child.lua_get("_G.State.config.models.selected"), "model2")
74+
end
75+
76+
T["EcaChatSelectModel"]["handles nil selection"] = function()
77+
-- Setup initial state
78+
child.lua([[
79+
_G.State.config.models.list = { "model1", "model2" }
80+
_G.State.config.models.selected = "model1"
81+
82+
-- Mock vim.ui.select to return nil (user cancelled)
83+
_G.mock_select(nil)
84+
]])
85+
86+
-- Execute command
87+
child.cmd("EcaChatSelectModel")
88+
89+
-- Check that state was NOT updated (still model1)
90+
eq(child.lua_get("_G.State.config.models.selected"), "model1")
91+
end
92+
93+
T["EcaChatSelectModel"]["displays all available models"] = function()
94+
-- Setup models list
95+
child.lua([[
96+
_G.State.config.models.list = { "gpt-4", "gpt-3.5-turbo", "claude-3" }
97+
98+
-- Mock vim.ui.select to capture the items shown
99+
_G.mock_select(nil)
100+
]])
101+
102+
-- Execute command
103+
child.cmd("EcaChatSelectModel")
104+
105+
-- Verify all models were shown
106+
local shown_items = child.lua_get("_G.shown_items")
107+
eq(shown_items[1], "gpt-4")
108+
eq(shown_items[2], "gpt-3.5-turbo")
109+
eq(shown_items[3], "claude-3")
110+
end
111+
112+
-- Test EcaChatSelectBehavior command
113+
T["EcaChatSelectBehavior"] = MiniTest.new_set()
114+
115+
T["EcaChatSelectBehavior"]["command is registered"] = function()
116+
local commands = child.lua_get("vim.api.nvim_get_commands({})")
117+
eq(type(commands.EcaChatSelectBehavior), "table")
118+
eq(commands.EcaChatSelectBehavior.name, "EcaChatSelectBehavior")
119+
end
120+
121+
T["EcaChatSelectBehavior"]["updates state when behavior selected"] = function()
122+
-- Setup initial state with behaviors
123+
child.lua([[
124+
_G.State.config.behaviors.list = { "helpful", "creative", "concise" }
125+
_G.State.config.behaviors.selected = "helpful"
126+
127+
-- Mock vim.ui.select to auto-select creative
128+
_G.mock_select("creative")
129+
]])
130+
131+
-- Execute command
132+
child.cmd("EcaChatSelectBehavior")
133+
134+
-- Check that state was updated
135+
eq(child.lua_get("_G.State.config.behaviors.selected"), "creative")
136+
end
137+
138+
T["EcaChatSelectBehavior"]["handles nil selection"] = function()
139+
-- Setup initial state
140+
child.lua([[
141+
_G.State.config.behaviors.list = { "helpful", "creative" }
142+
_G.State.config.behaviors.selected = "helpful"
143+
144+
-- Mock vim.ui.select to return nil (user cancelled)
145+
_G.mock_select(nil)
146+
]])
147+
148+
-- Execute command
149+
child.cmd("EcaChatSelectBehavior")
150+
151+
-- Check that state was NOT updated (still helpful)
152+
eq(child.lua_get("_G.State.config.behaviors.selected"), "helpful")
153+
end
154+
155+
T["EcaChatSelectBehavior"]["displays all available behaviors"] = function()
156+
-- Setup behaviors list
157+
child.lua([[
158+
_G.State.config.behaviors.list = { "helpful", "creative", "concise", "technical" }
159+
160+
-- Mock vim.ui.select to capture the items shown
161+
_G.mock_select(nil)
162+
]])
163+
164+
-- Execute command
165+
child.cmd("EcaChatSelectBehavior")
166+
167+
-- Verify all behaviors were shown
168+
local shown_items = child.lua_get("_G.shown_items")
169+
eq(shown_items[1], "helpful")
170+
eq(shown_items[2], "creative")
171+
eq(shown_items[3], "concise")
172+
eq(shown_items[4], "technical")
173+
end
174+
175+
return T

tests/test_server_integration.lua

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ end
4040
T["server"] = MiniTest.new_set()
4141

4242
T["server"]["start"] = function()
43-
child.lua("_G.server:start()")
43+
child.lua("_G.server:start({ clean = true })")
4444
child.lua([[
4545
_G.server_started = vim.wait(10000, function()
4646
return _G.server and _G.server:is_running()
@@ -52,7 +52,7 @@ T["server"]["start"] = function()
5252
end
5353

5454
T["server"]["start without initialize"] = function()
55-
child.lua("_G.server:start({ initialize = false })")
55+
child.lua("_G.server:start({ clean = true, initialize = false })")
5656
child.lua([[
5757
_G.server_started = vim.wait(10000, function()
5858
return _G.server and _G.server:is_running()
@@ -67,7 +67,7 @@ T["server"]["start with inexistent path"] = function()
6767
child.lua([[
6868
Config = require("eca.config")
6969
Config.setup({ server_path = "non-existing-path" } )
70-
_G.server:start()
70+
_G.server:start({ clean = true })
7171
]])
7272
child.lua([[
7373
_G.server_started = vim.wait(1000, function()

tests/test_server_path.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ local function setup_test_environment()
99
"nvim",
1010
"--headless",
1111
"--noplugin",
12+
"--clean",
1213
"--cmd",
1314
[[lua package.preload["eca.path_finder"] = function()
1415
local M = {}

0 commit comments

Comments
 (0)