105 lines
5.6 KiB
JavaScript
105 lines
5.6 KiB
JavaScript
import https from 'https';
|
||
import fs from 'fs';
|
||
|
||
// 本 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 CLI_PROXY_API = process.env.INPUT_CLI_PROXY_API || process.env.CLI_PROXY_API || '';
|
||
export const CLI_PROXY_API_KEY = process.env.INPUT_CLI_PROXY_API_KEY || process.env.CLI_PROXY_API_KEY || '';
|
||
export const LLM_PROVIDER = 'cliproxyapi';
|
||
|
||
export const FINDINGS_PATH = '.gitea/ai-review/findings.json';
|
||
export const EXCLUSIONS_PATH = '.gitea/ai-review/exclusions.json';
|
||
const MODEL_NAME_RE = /^[A-Za-z0-9._-]+$/;
|
||
|
||
function normalizeModelName(raw) {
|
||
const model = String(raw || '').trim();
|
||
if (!model) return { model: null, modelError: null };
|
||
if (!MODEL_NAME_RE.test(model)) {
|
||
return { model: null, modelError: '無效的 model 參數,僅允許英數字、點、底線與連字號' };
|
||
}
|
||
return { model, modelError: null };
|
||
}
|
||
|
||
let _insecureHttpsAgent = null;
|
||
/**
|
||
* 取得一個關閉 TLS 憑證驗證的 HTTPS Agent 單例,供連接使用自簽或無效憑證的內部服務時使用
|
||
* (例如自架 Gitea、CLIProxyAPI)。
|
||
*
|
||
* @remarks 首次呼叫時建立,之後快取為模組層級單例(singleton)重複使用,
|
||
* 避免每次都新建 Agent 與連線池、浪費 TCP 三次握手。
|
||
* 停用憑證驗證有中間人攻擊風險,僅限受信任的內部環境使用;
|
||
* 若需要完整 TLS 安全性,應改用預設 `https.Agent`,不要調用這個函式。
|
||
* @returns {import('https').Agent} 已關閉憑證驗證的 HTTPS Agent 單例。
|
||
*/
|
||
export function getInsecureHttpsAgent() {
|
||
return (_insecureHttpsAgent ??= new https.Agent({ rejectUnauthorized: false }));
|
||
}
|
||
|
||
// 過渡別名:既有呼叫端仍可用 OpenCode 語意名稱;新程式碼請直接使用 getInsecureHttpsAgent。
|
||
export const getOpenCodeHttpsAgent = getInsecureHttpsAgent;
|
||
|
||
/**
|
||
* 依環境變數解析並回傳 CLIProxyAPI 設定。
|
||
*
|
||
* 優先讀取 `INPUT_CLI_PROXY_API` / `CLI_PROXY_API` 作為 base URL(會 trim 並移除結尾斜線),
|
||
* `INPUT_MODEL` / `CLI_PROXY_API_MODEL` / `MODEL` / `OPENCODE_MODEL`(依序 fallback,
|
||
* 相容 action input、舊 OpenCode 設定與環境變數)作為可選模型名稱;若未提供,
|
||
* 則交由 CLIProxyAPI 自動選擇模型。若提供的名稱含非法字元,會被視為無效並於
|
||
* `modelError` 回報,`INPUT_CLI_PROXY_API_KEY` / `CLI_PROXY_API_KEY` 作為存取金鑰(會 trim)。
|
||
*
|
||
* 若 base URL 無法解析出任何值,視為沒有可用的 proxy 設定:`provider`/`baseURL` 回傳 `null`、
|
||
* `apiKeys` 回傳空陣列,但 `model`(若有解析到)仍會回傳,不會被清空。
|
||
*
|
||
* @returns {{ provider: ('cliproxyapi'|null), apiKeys: string[], baseURL: (string|null), model: (string|null), modelError: (string|null), command: null }}
|
||
* 設定物件;`provider` 為 `null` 表示沒有可用的 proxy 設定。
|
||
*/
|
||
export function getLLMConfig() {
|
||
const baseURL = String(process.env.INPUT_CLI_PROXY_API || process.env.CLI_PROXY_API || '').trim().replace(/\/$/, '');
|
||
const rawModel = process.env.INPUT_MODEL || process.env.CLI_PROXY_API_MODEL || process.env.MODEL || process.env.OPENCODE_MODEL || '';
|
||
const { model, modelError } = normalizeModelName(rawModel);
|
||
const apiKey = String(process.env.INPUT_CLI_PROXY_API_KEY || process.env.CLI_PROXY_API_KEY || '').trim();
|
||
if (!baseURL) return { provider: null, apiKeys: [], baseURL: null, model, modelError, command: null };
|
||
|
||
return {
|
||
provider: LLM_PROVIDER,
|
||
apiKeys: apiKey ? [apiKey] : [],
|
||
baseURL,
|
||
model,
|
||
modelError,
|
||
command: null,
|
||
};
|
||
}
|