Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,11 +159,16 @@ require("codereview").setup({
-- AI review
ai = {
enabled = true,
provider = "claude_cli", -- "claude_cli" | "anthropic" | "openai" | "ollama" | "custom_cmd"
provider = "claude_cli", -- "claude_cli" | "codex_cli" | "copilot_cli" | "gemini_cli" | "opencode_cli" | "qwen_cli" | "anthropic" | "openai" | "ollama" | "custom_cmd"
review_level = "info", -- "info" | "suggestion" | "warning" | "error"
max_file_size = 500, -- skip files larger than N lines (0 = unlimited)

claude_cli = { cmd = "claude", agent = "code-review" },
codex_cli = { cmd = "codex", model = nil },
copilot_cli = { cmd = "copilot", model = nil, agent = nil },
gemini_cli = { cmd = "gemini", model = nil },
opencode_cli = { cmd = "opencode", model = nil, agent = "plan", variant = nil },
qwen_cli = { cmd = "qwen", model = nil },
anthropic = { api_key = nil, model = "claude-sonnet-4-20250514" },
openai = { api_key = nil, model = "gpt-4o", base_url = nil },
ollama = { model = "llama3", base_url = "http://localhost:11434" },
Expand All @@ -185,6 +190,11 @@ Set `ai.provider` to choose a backend. Each provider has its own sub-table:
| Provider | Config key | Requirements |
|----------|-----------|--------------|
| Claude CLI | `claude_cli` | [Claude CLI](https://docs.anthropic.com/en/docs/claude-code) installed |
| Codex CLI | `codex_cli` | [Codex CLI](https://github.com/openai/codex) installed and authenticated |
| Copilot CLI | `copilot_cli` | [Copilot CLI](https://docs.github.com/en/copilot/concepts/agents/copilot-cli/about-copilot-cli) installed and authenticated |
| Gemini CLI | `gemini_cli` | [Gemini CLI](https://github.com/google-gemini/gemini-cli) installed and authenticated |
| OpenCode CLI | `opencode_cli` | [OpenCode CLI](https://github.com/anomalyco/opencode) installed and authenticated |
| Qwen CLI | `qwen_cli` | [Qwen CLI](https://github.com/QwenLM/qwen-code) installed and authenticated |
| Anthropic API | `anthropic` | `api_key` set |
| OpenAI | `openai` | `api_key` set |
| Ollama | `ollama` | Ollama running locally |
Expand Down
123 changes: 122 additions & 1 deletion doc/codereview.txt
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ Options for AI review. See |codereview-ai-review|.
-- boolean enable AI review features
enabled = true,

-- "claude_cli" | "anthropic" | "openai" | "ollama" | "custom_cmd"
-- "claude_cli" | "codex_cli" | "copilot_cli" | "gemini_cli" | "opencode_cli" | "qwen_cli" | "anthropic" | "openai" | "ollama" | "custom_cmd"
provider = "claude_cli",

-- "info" | "suggestion" | "warning" | "error" minimum severity to display
Expand All @@ -321,6 +321,29 @@ Options for AI review. See |codereview-ai-review|.
cmd = "claude", -- path or name of the claude binary
agent = "code-review", -- Claude agent/profile name
},
codex_cli = {
cmd = "codex", -- path or name of the codex binary
model = nil, -- optional model override
},
copilot_cli = {
cmd = "copilot", -- path or name of the copilot binary
model = nil, -- optional model override
agent = nil, -- optional agent override
},
gemini_cli = {
cmd = "gemini", -- path or name of the gemini binary
model = nil, -- optional model override
},
opencode_cli = {
cmd = "opencode", -- path or name of the opencode binary
model = nil, -- optional model override
agent = "plan", -- optional agent override
variant = nil, -- optional model variant (provider-specific)
},
qwen_cli = {
cmd = "qwen", -- path or name of the qwen binary
model = nil, -- optional model override
},
anthropic = {
api_key = nil, -- falls back to ANTHROPIC_API_KEY
model = "claude-sonnet-4-20250514",
Expand Down Expand Up @@ -380,6 +403,104 @@ Requirements:
}
<

------------------------------------------------------------------------------
Codex CLI *codereview-ai-codex-cli*

Uses the `codex exec` command-line interface to prompt non-interactively using
the currently configured or default model until explicitly specified.

Requirements:
- `codex` binary installed and on PATH
- Codex CLI already authenticated
>lua
ai = {
provider = "codex_cli",
codex_cli = {
cmd = "codex", -- binary name or full path
model = "gpt-5.4", -- optional model override
},
}
<

------------------------------------------------------------------------------
Copilot CLI *codereview-ai-copilot-cli*

Uses the `copilot` command-line interface to prompt non-interactively using
the currently configured or default model until explicitly specified.

Requirements:
- `copilot` binary installed and on PATH
- Copilot CLI already authenticated
>lua
ai = {
provider = "copilot_cli",
copilot_cli = {
cmd = "copilot", -- binary name or full path
model = "claude-opus-4.6", -- optional model override
agent = nil -- specify a custom agent to use
},
}
<

------------------------------------------------------------------------------
Gemini CLI *codereview-ai-gemini-cli*

Uses the `gemini` command-line interface to prompt non-interactively using
the currently configured or default model until explicitly specified.

Requirements:
- `gemini` binary installed and on PATH
- Gemini CLI already authenticated
>lua
ai = {
provider = "gemini_cli",
gemini_cli = {
cmd = "gemini", -- binary name or full path
model = "gemini-3-pro", -- optional model override
},
}
<

------------------------------------------------------------------------------
OpenCode CLI *codereview-ai-opencode-cli*

Uses the `opencode run` command-line interface to prompt non-interactively using
the currently configured or default model until explicitly specified.

Requirements:
- `opencode` binary installed and on PATH
- OpenCode CLI already authenticated
>lua
ai = {
provider = "opencode_cli",
opencode_cli = {
cmd = "opencode", -- binary name or full path
model = "github-copilot/claude-sonnet-4.6", -- optional provider/model
agent = "plan", -- optional agent to use
variant = nil, -- model variant
},
}
<

------------------------------------------------------------------------------
Qwen CLI *codereview-ai-qwen-cli*

Uses the `qwen` command-line interface to prompt non-interactively using
the currently configured or default model until explicitly specified.

Requirements:
- `qwen` binary installed and on PATH
- Qwen CLI already authenticated
>lua
ai = {
provider = "qwen_cli",
qwen_cli = {
cmd = "qwen", -- binary name or full path
model = "qwen3-coder-plus", -- optional model override
},
}
<

------------------------------------------------------------------------------
Anthropic API *codereview-ai-anthropic*

Expand Down
68 changes: 2 additions & 66 deletions lua/codereview/ai/providers/claude_cli.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
local config = require("codereview.config")
local log = require("codereview.log")
local utils = require("codereview.ai.providers.utils")
local M = {}

function M.build_cmd(claude_cmd, agent)
Expand All @@ -23,71 +23,7 @@ function M.run(prompt, callback, opts)
local claude_cmd = pcfg.cmd or cfg.ai.claude_cmd or "claude"
local agent = (not opts.skip_agent) and (pcfg.agent or cfg.ai.agent) or nil
local cmd = M.build_cmd(claude_cmd, agent)
log.debug("AI claude_cli: starting " .. table.concat(cmd, " "))
local stdout_chunks = {}
local stderr_chunks = {}
local done = false

local job_id = vim.fn.jobstart(cmd, {
stdout_buffered = true,
stderr_buffered = true,
on_stdout = function(_, data)
if data then
for i, chunk in ipairs(data) do
if i < #data or chunk ~= "" then
table.insert(stdout_chunks, chunk)
end
end
end
end,
on_stderr = function(_, data)
if data then
for i, chunk in ipairs(data) do
if i < #data or chunk ~= "" then
table.insert(stderr_chunks, chunk)
end
end
end
end,
on_exit = function(_, code)
if done then
return
end
done = true
vim.schedule(function()
local stderr_str = table.concat(stderr_chunks, "\n")
if stderr_str ~= "" then
log.warn("AI claude_cli stderr: " .. stderr_str)
end
local output = table.concat(stdout_chunks, "\n")
if code ~= 0 then
local msg = "Claude CLI exited with code " .. code
if output ~= "" then
msg = msg .. "\n" .. output
end
if stderr_str ~= "" then
msg = msg .. "\n" .. stderr_str
end
log.error("AI claude_cli: " .. msg)
callback(nil, msg)
else
log.debug("AI claude_cli: completed, output length=" .. #output)
callback(output)
end
end)
end,
})

if job_id <= 0 then
local msg = "Failed to start Claude CLI. Is '" .. claude_cmd .. "' in your PATH?"
log.error("AI claude_cli: " .. msg)
callback(nil, msg)
return
end

vim.fn.chansend(job_id, prompt)
vim.fn.chanclose(job_id, "stdin")
return job_id
return utils.run_cli(prompt, callback, cmd)
end

return M
28 changes: 28 additions & 0 deletions lua/codereview/ai/providers/codex_cli.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
local config = require("codereview.config")
local utils = require("codereview.ai.providers.utils")
local M = {}

function M.build_cmd(codex_cmd, model)
local cmd = { codex_cmd or "codex", "exec" }
table.insert(cmd, "--sandbox=read-only")
if model and model ~= "" then
table.insert(cmd, "--model")
table.insert(cmd, model)
end
table.insert(cmd, "--")
return cmd
end

function M.run(prompt, callback)
local cfg = config.get()
if not cfg.ai.enabled then
callback(nil, "AI review is disabled in config")
return
end

local pcfg = cfg.ai.codex_cli or {}
local cmd = M.build_cmd(pcfg.cmd, pcfg.model)
return utils.run_cli(prompt, callback, cmd)
end

return M
32 changes: 32 additions & 0 deletions lua/codereview/ai/providers/copilot_cli.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
local config = require("codereview.config")
local utils = require("codereview.ai.providers.utils")
local M = {}

function M.build_cmd(copilot_cmd, model, agent)
local cmd = { copilot_cmd or "copilot" }
table.insert(cmd, "--silent")
table.insert(cmd, "--no-auto-update")
if model and model ~= "" then
table.insert(cmd, "--model")
table.insert(cmd, model)
end
if agent and agent ~= "" then
table.insert(cmd, "--agent")
table.insert(cmd, agent)
end
return cmd
end

function M.run(prompt, callback)
local cfg = config.get()
if not cfg.ai.enabled then
callback(nil, "AI review is disabled in config")
return
end

local pcfg = cfg.ai.copilot_cli or {}
local cmd = M.build_cmd(pcfg.cmd, pcfg.model, pcfg.agent)
return utils.run_cli(prompt, callback, cmd)
end

return M
Loading
Loading