Skip to content

Commit a8cbf87

Browse files
authored
Merge branch 'main' into develop
2 parents f862c5f + 294eddc commit a8cbf87

File tree

10 files changed

+183
-10
lines changed

10 files changed

+183
-10
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Thank you to the following people:
3030

3131
- :speech_balloon: [Copilot Chat](https://github.com/features/copilot) meets [Zed AI](https://zed.dev/blog/zed-ai), in Neovim
3232
- :electric_plug: Support for LLMs from Anthropic, Copilot, GitHub Models, DeepSeek, Gemini, Mistral AI, Novita, Ollama, OpenAI, Azure OpenAI, HuggingFace and xAI (or [bring your own](https://codecompanion.olimorris.dev/extending/adapters.html))
33-
- :robot: Support for [Agent Client Protocol](https://agentclientprotocol.com/overview/introduction), enabling coding with agents like [Augment Code](https://docs.augmentcode.com/cli/overview), [Cagent](https://github.com/docker/cagent) from Docker, [Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview), [Codex](https://openai.com/codex), [Gemini CLI](https://github.com/google-gemini/gemini-cli), [Goose](https://block.github.io/goose/), [Kimi CLI](https://github.com/MoonshotAI/kimi-cli), [Mistral Vibe](https://github.com/mistralai/mistral-vibe) and [OpenCode](https://opencode.ai)
33+
- :robot: Support for [Agent Client Protocol](https://agentclientprotocol.com/overview/introduction), enabling coding with agents like [Augment Code](https://docs.augmentcode.com/cli/overview), [Cagent](https://github.com/docker/cagent) from Docker, [Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview), [Codex](https://openai.com/codex), [Gemini CLI](https://github.com/google-gemini/gemini-cli), [Goose](https://block.github.io/goose/), [Kimi CLI](https://github.com/MoonshotAI/kimi-cli), [Kiro](https://kiro.dev/docs/cli/), [Mistral Vibe](https://github.com/mistralai/mistral-vibe) and [OpenCode](https://opencode.ai)
3434
- :heart_hands: User contributed and supported [adapters](https://codecompanion.olimorris.dev/configuration/adapters#community-adapters)
3535
- :rocket: [Inline transformations](https://codecompanion.olimorris.dev/usage/inline-assistant.html), code creation and refactoring
3636
- :art: [Variables](https://codecompanion.olimorris.dev/usage/chat-buffer/variables.html), [Slash Commands](https://codecompanion.olimorris.dev/usage/chat-buffer/slash-commands.html), [Tools](https://codecompanion.olimorris.dev/usage/chat-buffer/tools.html) and [Workflows](https://codecompanion.olimorris.dev/usage/workflows.html) to improve LLM output

doc/codecompanion.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1211,6 +1211,14 @@ CLI in order to setup your API key. Then ensure that in your chat buffer you
12111211
select the `mistral_vibe` adapter.
12121212

12131213

1214+
SETUP: KIRO CLI ~
1215+
1216+
Install Kiro cli <https://kiro.dev/docs/cli/> as per their instructions. Then
1217+
open it and login (if installation doesn’t already prompt you to login). the
1218+
codecompanion adapter will execute `kiro-cli acp`, make sure to have it
1219+
available on your PATH.
1220+
1221+
12141222
SETUP: OPENCODE ~
12151223

12161224
To use OpenCode <https://opencode.ai> in CodeCompanion, ensure you’ve

doc/configuration/adapters-acp.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,10 @@ To use [Goose](https://block.github.io/goose/) in CodeCompanion, ensure you've f
272272

273273
Install [Kimi CLI](https://github.com/MoonshotAI/kimi-cli?tab=readme-ov-file#installation) as per their instructions. Then in the CLI, run `kimi` followed by `/login` to configure your API key. Then ensure that in your chat buffer you select the `kimi_cli` adapter.
274274

275+
## Setup: Kiro CLI
276+
277+
Install [Kiro cli](https://kiro.dev/docs/cli/) as per their instructions. Then open it and login (if installation doesn't already prompt you to login). the codecompanion adapter will execute `kiro-cli acp`, make sure to have it available on your PATH.
278+
275279
## Setup: Mistral Vibe
276280

277281
To use [Mistral Vibe](https://github.com/mistralai/mistral-vibe) in CodeCompanion, ensure you've followed their documentation to [install](https://github.com/mistralai/mistral-vibe). Then run `vibe --setup` in your CLI in order to setup your API key. Then ensure that in your chat buffer you select the `mistral_vibe` adapter.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
local helpers = require("codecompanion.adapters.acp.helpers")
2+
3+
---@class CodeCompanion.ACPAdapter.Kiro: CodeCompanion.ACPAdapter
4+
return {
5+
name = "kiro",
6+
formatted_name = "Kiro",
7+
type = "acp",
8+
roles = {
9+
llm = "assistant",
10+
user = "user",
11+
},
12+
opts = {
13+
vision = true,
14+
},
15+
commands = {
16+
default = {
17+
"kiro-cli",
18+
"acp",
19+
},
20+
},
21+
defaults = {
22+
mcpServers = {},
23+
timeout = 20000, -- 20 seconds
24+
},
25+
parameters = {
26+
protocolVersion = 1,
27+
clientCapabilities = {
28+
fs = { readTextFile = true, writeTextFile = true },
29+
},
30+
clientInfo = {
31+
name = "CodeCompanion.nvim",
32+
version = "1.0.0",
33+
},
34+
},
35+
handlers = {
36+
---@param self CodeCompanion.ACPAdapter
37+
---@return boolean
38+
setup = function(self)
39+
return true
40+
end,
41+
42+
---Manually handle authentication
43+
---@param self CodeCompanion.ACPAdapter
44+
---@return boolean
45+
auth = function(self)
46+
-- kiro-cli handles authentication exclusively through its kiro-cli CLI interface
47+
-- Users are expected to login there and then can use the ACP after. auth is therefore
48+
-- declared a success here to work around attempted authentication.
49+
return true
50+
end,
51+
52+
---@param self CodeCompanion.ACPAdapter
53+
---@param messages table
54+
---@param capabilities table
55+
---@return table
56+
form_messages = function(self, messages, capabilities)
57+
return helpers.form_messages(self, messages, capabilities)
58+
end,
59+
60+
---Function to run when the request has completed. Useful to catch errors
61+
---@param self CodeCompanion.ACPAdapter
62+
---@param code number
63+
---@return nil
64+
on_exit = function(self, code) end,
65+
},
66+
}

lua/codecompanion/adapters/http/openai_compatible.lua

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,7 @@ local function get_models(self, opts)
4343
adapter_utils.get_env_vars(adapter, { timeout = config.adapters.opts.cmd_timeout })
4444
local url = adapter.env_replaced.url .. adapter.env_replaced.models_endpoint
4545

46-
local headers = {
47-
["content-type"] = "application/json",
48-
}
49-
if adapter.env_replaced.api_key then
50-
headers["Authorization"] = "Bearer " .. adapter.env_replaced.api_key
51-
end
46+
local headers = adapter_utils.set_env_vars(adapter, adapter.headers) or {}
5247

5348
local ok, response, json
5449

lua/codecompanion/adapters/http/openai_responses.lua

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,12 @@ return {
181181
}
182182

183183
-- If next message is also from user with text content, combine them
184-
if next_msg and next_msg.role == m.role and type(next_msg.content) == "string" then
184+
if
185+
next_msg
186+
and next_msg.role == m.role
187+
and type(next_msg.content) == "string"
188+
and not (next_msg._meta and next_msg._meta.tag == "image")
189+
then
185190
table.insert(combined_content, {
186191
type = "input_text",
187192
text = next_msg.content,

lua/codecompanion/config.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ local defaults = {
4343
gemini_cli = "gemini_cli",
4444
goose = "goose",
4545
kimi_cli = "kimi_cli",
46+
kiro = "kiro",
4647
mistral_vibe = "mistral_vibe",
4748
opencode = "opencode",
4849
opts = {

lua/codecompanion/utils/init.lua

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,14 +108,20 @@ function M.extract_all_placeholders(prompts)
108108
return all_placeholders
109109
end
110110

111+
---@param str string The string to escape percent signs in for gsub replacement
112+
---@return string The escaped string, safe for use as a gsub replacement
113+
local function escape_gsub_replacement(str)
114+
return (str:gsub("%%", "%%%%"))
115+
end
116+
111117
---Replace any placeholders (e.g. ${placeholder}) in a string or table
112118
---@param t table|string The content to process
113119
---@param replacements table<string, string> Map of placeholder names to replacement values
114120
---@return string|nil The replaced string if input was string, or nil if input was table (modified in place)
115121
function M.replace_placeholders(t, replacements)
116122
if type(t) == "string" then
117123
for placeholder, replacement in pairs(replacements) do
118-
t = t:gsub("%${" .. vim.pesc(placeholder) .. "}", replacement)
124+
t = t:gsub("%${" .. vim.pesc(placeholder) .. "}", escape_gsub_replacement(replacement))
119125
end
120126
return t
121127
else
@@ -124,7 +130,7 @@ function M.replace_placeholders(t, replacements)
124130
M.replace_placeholders(value, replacements)
125131
elseif type(value) == "string" then
126132
for placeholder, replacement in pairs(replacements) do
127-
value = value:gsub("%${" .. vim.pesc(placeholder) .. "}", replacement)
133+
value = value:gsub("%${" .. vim.pesc(placeholder) .. "}", escape_gsub_replacement(replacement))
128134
end
129135
t[key] = value
130136
end

tests/adapters/http/test_openai_responses.lua

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,74 @@ T["Responses"]["build_messages"]["images"] = function()
153153
h.eq(expected, adapter.handlers.request.build_messages(adapter, messages))
154154
end
155155

156+
T["Responses"]["build_messages"]["multiple consecutive images are not merged as text"] = function()
157+
local messages = {
158+
{
159+
content = "img1_base64",
160+
role = "user",
161+
opts = { visible = false },
162+
context = { mimetype = "image/png" },
163+
_meta = { tag = "image" },
164+
},
165+
{
166+
content = "img2_base64",
167+
role = "user",
168+
opts = { visible = false },
169+
context = { mimetype = "image/png" },
170+
_meta = { tag = "image" },
171+
},
172+
{
173+
content = "img3_base64",
174+
role = "user",
175+
opts = { visible = false },
176+
context = { mimetype = "image/png" },
177+
_meta = { tag = "image" },
178+
},
179+
{
180+
content = "How many images do you see?",
181+
role = "user",
182+
},
183+
}
184+
185+
local expected = {
186+
input = {
187+
{
188+
role = "user",
189+
content = {
190+
{
191+
type = "input_image",
192+
image_url = "data:image/png;base64,img1_base64",
193+
},
194+
},
195+
},
196+
{
197+
role = "user",
198+
content = {
199+
{
200+
type = "input_image",
201+
image_url = "data:image/png;base64,img2_base64",
202+
},
203+
},
204+
},
205+
{
206+
role = "user",
207+
content = {
208+
{
209+
type = "input_image",
210+
image_url = "data:image/png;base64,img3_base64",
211+
},
212+
{
213+
type = "input_text",
214+
text = "How many images do you see?",
215+
},
216+
},
217+
},
218+
},
219+
}
220+
221+
h.eq(expected, adapter.handlers.request.build_messages(adapter, messages))
222+
end
223+
156224
T["Responses"]["build_messages"]["format tool calls"] = function()
157225
local messages = {
158226
{

tests/utils/test_utils.lua

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,26 @@ T["Utils"]["replace_placeholders()"]["handles special characters in placeholder
200200
h.eq(result, "Value: test")
201201
end
202202

203+
T["Utils"]["replace_placeholders()"]["handles percent signs in replacement values"] = function()
204+
local result = child.lua([[
205+
return utils.replace_placeholders(
206+
'(<a href="https://my.test.page/tester.php?login=${email}" title="Test Page">%20</a>)',
207+
{ email = "testuser%40testing.org" }
208+
)
209+
]])
210+
h.eq(result, '(<a href="https://my.test.page/tester.php?login=testuser%40testing.org" title="Test Page">%20</a>)')
211+
end
212+
213+
T["Utils"]["replace_placeholders()"]["handles simple percent signs in replacement"] = function()
214+
local result = child.lua([[
215+
return utils.replace_placeholders(
216+
"Encoded: ${value}",
217+
{ value = "%20" }
218+
)
219+
]])
220+
h.eq(result, "Encoded: %20")
221+
end
222+
203223
T["Utils"]["resolve_nested_value()"] = MiniTest.new_set()
204224

205225
T["Utils"]["resolve_nested_value()"]["resolves top-level value"] = function()

0 commit comments

Comments
 (0)