Files
ai-code-review/src/config.js
T
Jeffery 7c35dc0601
CI / 2. TEST (pull_request) Failing after 1s
CI / 3. RESULT (pull_request) Has been skipped
CI / 1. BUILD (pull_request) Successful in 2s
fix(config): comment token 缺省時回退至 token
2026-07-02 18:13:27 +08:00

128 lines
5.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import https from 'https';
import fs from 'fs';
import { execFileSync } from 'child_process';
// 本 action 會連接自架 Gitea / OpenCode,部署環境可能使用內部 CA 或自簽憑證。
// 對外部服務請優先使用預設 TLS 驗證;需要內部服務相容時才使用 getInsecureHttpsAgent()。
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
/**
* 讀取 runner 寫入的事件 payload JSON`GITHUB_EVENT_PATH` / `GITEA_EVENT_PATH`)。
* 讀不到或解析失敗時回傳空物件,讓後續取值一律走 fallback,不讓 import 期噴錯。
*
* @returns {Record<string, any>} 事件 payload 物件,失敗時為 `{}`。
*/
function readEventPayload() {
const eventPath = process.env.GITHUB_EVENT_PATH || process.env.GITEA_EVENT_PATH;
if (!eventPath) return {};
try {
return JSON.parse(fs.readFileSync(eventPath, 'utf8'));
} catch {
return {};
}
}
const EVENT = readEventPayload();
const PR = EVENT.pull_request || {};
// 取值優先序:有對應 `with:` 輸入的欄位一律 INPUT_* 優先(使用端明確傳入的值最權威,
// 蓋過環境中剛好存在的 ambient env)→ 專用 env(相容舊 Docker 版與測試)→ 內建預設。
// 無對應輸入的欄位(server url / repository / PR_*)則走專用 env → runner 內建 / 事件 payload。
// 使用端 workflow 只需傳 `with: token`。
export const GITEA_TOKEN = process.env.INPUT_TOKEN || process.env.GITEA_TOKEN || '';
export const GITEA_COMMENT_TOKEN = process.env.INPUT_COMMENT_TOKEN || process.env.GITEA_COMMENT_TOKEN || process.env.INPUT_TOKEN || process.env.GITEA_TOKEN || '';
export const GITEA_SERVER_URL = process.env.GITEA_SERVER_URL || process.env.GITHUB_SERVER_URL || 'https://gitea.com';
export const GITEA_REPOSITORY = process.env.GITEA_REPOSITORY || process.env.GITHUB_REPOSITORY || '';
export const PR_NUMBER = process.env.PR_NUMBER || (PR.number != null ? String(PR.number) : '');
export const PR_HEAD_SHA = process.env.PR_HEAD_SHA || PR.head?.sha || process.env.GITHUB_SHA || '';
export const PR_HEAD_BRANCH = process.env.PR_HEAD_BRANCH || PR.head?.ref || process.env.GITHUB_HEAD_REF || '';
export const PR_BASE_BRANCH = process.env.PR_BASE_BRANCH || PR.base?.ref || process.env.GITHUB_BASE_REF || '';
export const FINDINGS_PATH = '.gitea/ai-review/findings.json';
export const EXCLUSIONS_PATH = '.gitea/ai-review/exclusions.json';
/**
* 建立一個停用 TLS 憑證驗證(`rejectUnauthorized: false`)的 HTTPS Agent
* 供連接使用自簽或無效憑證的內部服務時使用。
*
* @remarks 首次呼叫時建立,之後快取為模組層級單例(singleton)重複使用,
* 避免每次都新建 Agent 與連線池、浪費 TCP 三次握手。
* 停用憑證驗證有中間人攻擊風險,僅限受信任的內部環境使用。
* @returns {import('https').Agent} 已關閉憑證驗證的 HTTPS Agent 單例。
*/
let _insecureHttpsAgent = null;
export function getInsecureHttpsAgent() {
return (_insecureHttpsAgent ??= new https.Agent({ rejectUnauthorized: false }));
}
// 過渡別名:既有呼叫端仍可用 OpenCode 語意名稱;新程式碼請直接使用 getInsecureHttpsAgent。
export const getOpenCodeHttpsAgent = getInsecureHttpsAgent;
const CLI_CANDIDATES = [
{
provider: 'codex',
command: 'codex',
defaultModel: 'gpt-5.4-mini',
},
{
provider: 'claude',
command: 'claude',
defaultModel: 'sonnet',
},
{
provider: 'antigravity',
command: 'agy',
defaultModel: 'gemini-2.5-flash',
},
{
provider: 'antigravity',
command: 'antigravity',
defaultModel: 'gemini-2.5-flash',
},
{
provider: 'opencode',
command: 'opencode',
defaultModel: 'google/gemini-2.5-flash',
},
];
export function getLLMCLICommands() {
return CLI_CANDIDATES.map(c => c.command);
}
function commandExists(command) {
try {
execFileSync('/bin/sh', ['-lc', `command -v ${command}`], { stdio: 'ignore' });
return true;
} catch {
return false;
}
}
/**
* 依環境變數解析並回傳 LLM 提供者設定。
*
* 優先使用 `AI_ASSISTANT_CLI` 指定的 CLI;未指定時依序偵測 codex、claude、antigravity、opencode。
* model 依序取 `with: model``INPUT_MODEL`)、`MODEL`、相容舊的 `OPENCODE_MODEL`,最後用各 CLI 預設值。
*
* @param {{ commandExistsFn?: (command: string) => boolean }} [deps] - 可注入的 CLI 偵測函式,供測試使用。
* @returns {{ provider: ('codex'|'claude'|'antigravity'|'opencode'|null), apiKeys: string[], baseURL: null, model: (string|null), command: (string|null) }}
* LLM 設定物件;`provider` 為 `null` 表示沒有可用的提供者。
*/
export function getLLMConfig({ commandExistsFn = commandExists } = {}) {
const requested = process.env.AI_ASSISTANT_CLI;
const candidates = requested
? CLI_CANDIDATES.filter(c => c.provider === requested || c.command === requested)
: CLI_CANDIDATES;
const cli = candidates.find(c => commandExistsFn(c.command));
if (!cli) return { provider: null, apiKeys: [], baseURL: null, model: null, command: null };
return {
provider: cli.provider,
apiKeys: [cli.provider],
baseURL: null,
model: process.env.INPUT_MODEL || process.env.MODEL || process.env.OPENCODE_MODEL || cli.defaultModel,
command: cli.command,
};
}