feat(ai-code-review): 改用 AI 助理 CLI 執行審查

This commit is contained in:
2026-06-27 13:30:40 +00:00
parent 93be261b90
commit 08a72a8c7f
4 changed files with 177 additions and 223 deletions
+11 -53
View File
@@ -6,7 +6,6 @@ import {
GITEA_REPOSITORY,
PR_NUMBER,
getInsecureHttpsAgent,
getOpenCodeHttpsAgent,
getLLMConfig,
} from './config.js';
import { verifyRemoteAccess } from './git.js';
@@ -30,33 +29,6 @@ const api = (path) => `${GITEA_SERVER_URL.replace(/\/$/, '')}/api/v1${path}`;
* @returns {{Authorization: string, 'Content-Type': string}} 可直接交給 axios 的 headers 物件。
*/
const giteaHeaders = (token) => ({ Authorization: `token ${token}`, 'Content-Type': 'application/json' });
/**
* 將模型字串解析為 OpenCode 的 providerID 與 modelID。
*
* 若 model 含 `/` 則以斜線拆分為 provider/model;否則 provider 取
* 環境變數 OPENCODE_PROVIDER(預設 `google`),model 即原字串。
* @param {string} model - 模型識別字串,例如 `google/gemini-2.0` 或 `gemini-2.0`。
* @returns {{providerID: string, modelID: string}} 解析後的 provider 與 model 識別碼。
* @remarks 讀取 process.env.OPENCODE_PROVIDERsplit 上限為 2 段,多餘段落會被忽略。
*/
const opencodeModelConfig = (model) => {
const [providerID, modelID] = model.includes('/') ? model.split('/', 2) : [process.env.OPENCODE_PROVIDER || 'google', model];
return { providerID, modelID };
};
/**
* 組出呼叫 OpenCode server 用的 axios 請求設定。
*
* 固定 30 秒逾時,並套用 config.js 的 getOpenCodeHttpsAgent() 作為 httpsAgent。
* @param {object} headers - 要套用的 HTTP headers 物件。
* @returns {{headers: object, timeout: number, httpsAgent: import('https').Agent}} axios 設定物件。
* @remarks 每次呼叫都會執行 getOpenCodeHttpsAgent() 取得 agent。
*/
const opencodeAxiosOptions = (headers) => ({
headers,
timeout: 30000,
httpsAgent: getOpenCodeHttpsAgent(),
});
/**
* 將(axios)錯誤格式化為易讀的訊息字串。
*
@@ -124,37 +96,23 @@ export async function verifyCommentToken(token = GITEA_COMMENT_TOKEN) {
}
/**
* 驗證 LLMOpenCode server)設定可用。
* 驗證 LLMAI 助理 CLI)設定可用。
*
* 依序確認:已設定 provider、有 base URL、health 端點可連線、OpenCode 已設定
* 對應 provider 且其列出指定 model。任一不符回傳對應錯誤;錯誤被攔截不會 throw
* 確認目前環境可偵測到支援的 CLI,且已解析出 model。實際模型可用性由 CLI
* 在正式呼叫時回報;preflight 不主動送 prompt,避免額外消耗額度
* @returns {Promise<{ok: true, provider: string}|{ok: false, provider?: string, error: string}>}
* 通過時含 provider;未設定 provider 的失敗分支不含 provider 欄位。
* @remarks 設定來源為 config.js 的 getLLMConfig()provider/model 鍵的比對使用 opencodeModelConfig 解析後的 providerID/modelID
* @remarks 設定來源為 config.js 的 getLLMConfig()。
*/
export async function verifyLLM() {
const { provider, baseURL, model } = getLLMConfig();
if (!provider) return { ok: false, error: '未設定 OpenCode server,請設定 OPENCODE_BASE_URL' };
if (!baseURL) return { ok: false, provider, error: `${provider} 缺少 base URL` };
const base = baseURL.replace(/\/$/, '');
const headers = { 'Content-Type': 'application/json' };
const { providerID, modelID } = opencodeModelConfig(model);
try {
await axios.get(`${base}/global/health`, opencodeAxiosOptions(headers));
const providers = await axios.get(`${base}/config/providers`, opencodeAxiosOptions(headers));
const configuredProvider = providers.data.providers?.find(p => p.id === providerID);
if (!configuredProvider) return { ok: false, provider, error: `OpenCode server 未設定 provider=${providerID}` };
if (!configuredProvider.models?.[modelID]) return { ok: false, provider, error: `OpenCode server provider=${providerID} 未列出 model=${modelID}` };
return { ok: true, provider };
} catch (e) {
return { ok: false, provider, error: `OpenCode server 驗證失敗: ${e.message}` };
}
const { provider, command, model } = getLLMConfig();
if (!provider || !command) return { ok: false, error: '未偵測到可用 AI 助理 CLI,請安裝 codex、claude 或 opencode' };
if (!model) return { ok: false, provider, error: '未設定 MODEL' };
return { ok: true, provider, command, model };
}
/**
* 執行所有前置驗證(Step2):環境變數、Gitea token、comment token、git 遠端、LLM。
* 執行所有前置驗證(Step2):環境變數、Gitea token、comment token、git 遠端、LLM CLI
*
* 全程唯讀,不發布任何 comment;任一檢查失敗即記錄錯誤並回傳 false。
* 各檢查可經 deps 注入覆寫,方便單元測試。
@@ -164,7 +122,7 @@ export async function verifyLLM() {
* @param {Function} [deps.verifyToken=verifyGiteaToken] - Gitea token / repo 讀取驗證。
* @param {Function} [deps.verifyComment=verifyCommentToken] - comment token 驗證。
* @param {Function} [deps.verifyRemote=verifyRemoteAccess] - git 遠端(ls-remote)認證驗證。
* @param {Function} [deps.verifyLLMFn=verifyLLM] - LLMOpenCode)連線驗證。
* @param {Function} [deps.verifyLLMFn=verifyLLM] - LLMAI 助理 CLI驗證。
* @returns {Promise<boolean>} 全部通過為 true,任一失敗為 false。
* @remarks 透過 log.js 輸出 step/ok/line/error/result 記錄;不會 throw(前提是注入的檢查函式皆自行攔截錯誤)。
*/
@@ -212,7 +170,7 @@ export async function runPreflight(workspace = process.env.GITHUB_WORKSPACE || '
error(`LLM 驗證失敗: ${llm.error}`);
return false;
}
ok(`LLM provider=${llm.provider} 連線正常`);
ok(`LLM provider=${llm.provider} CLI 可用`);
result(true, '前置驗證通過');
return true;