Files
telegram-codex-bot/src/config.js
T

113 lines
4.1 KiB
JavaScript
Raw Normal View History

'use strict';
// 設定載入與驗證。優先序:env 變數 > 設定檔 > 預設值。
const fs = require('fs');
const path = require('path');
const CONFIG_BASENAME = 'tgcodex.config.json';
const SANDBOX_MODES = ['read-only', 'workspace-write', 'danger-full-access'];
const DEFAULTS = {
// Telegram Bot tokenBotFather 取得)
telegramToken: '',
// Codex 可讀寫的專案目錄(相對路徑以設定檔所在目錄為基準)
workDir: '.',
// Codex 沙盒模式,預設可讀寫專案目錄
sandbox: 'workspace-write',
// 指定模型;空字串 = 使用 codex 預設
model: '',
// 推理強度(low/medium/high/xhigh/max/ultra…依模型而定);空字串 = codex 預設
reasoningEffort: '',
// 速度固定 Fastpriority = 1.5x);要改只能手動編輯設定檔
serviceTier: 'priority',
// 單次回應逾時(分鐘)
timeoutMinutes: 30,
// pm2 程序名稱(web 檢視工具會叫 <pm2Name>-web
pm2Name: 'tgcodex-bot',
// 內建 PM2 web 檢視工具
web: { enabled: true, port: 3799, host: '127.0.0.1' },
// 手動指定 codex 入口(codex.js 或執行檔路徑);空字串 = 自動偵測
codexPath: '',
};
function findConfigPath(explicit) {
if (explicit) return path.resolve(explicit);
if (process.env.TGCODEX_CONFIG) return path.resolve(process.env.TGCODEX_CONFIG);
return path.resolve(process.cwd(), CONFIG_BASENAME);
}
function loadConfig(explicitPath) {
const file = findConfigPath(explicitPath);
let raw = {};
if (fs.existsSync(file)) {
try {
raw = JSON.parse(fs.readFileSync(file, 'utf-8'));
} catch (err) {
throw new Error(`設定檔 ${file} 不是合法 JSON${err.message}`);
}
}
const cfg = {
...DEFAULTS,
...raw,
web: { ...DEFAULTS.web, ...(raw.web || {}) },
};
if (process.env.TGCODEX_TOKEN) cfg.telegramToken = process.env.TGCODEX_TOKEN;
if (process.env.TGCODEX_WORKDIR) cfg.workDir = process.env.TGCODEX_WORKDIR;
if (process.env.TGCODEX_SANDBOX) cfg.sandbox = process.env.TGCODEX_SANDBOX;
if (process.env.TGCODEX_MODEL) cfg.model = process.env.TGCODEX_MODEL;
if (process.env.TGCODEX_WEB_PORT) cfg.web.port = Number(process.env.TGCODEX_WEB_PORT);
if (process.env.TGCODEX_WEB_HOST) cfg.web.host = process.env.TGCODEX_WEB_HOST;
cfg.configPath = file;
cfg.configDir = path.dirname(file);
cfg.workDir = path.resolve(cfg.configDir, cfg.workDir);
cfg.stateDir = path.join(cfg.configDir, '.tgcodex');
cfg.logsDir = path.join(cfg.stateDir, 'logs');
cfg.tmpDir = path.join(cfg.stateDir, 'tmp');
if (!SANDBOX_MODES.includes(cfg.sandbox)) {
throw new Error(`sandbox 必須是 ${SANDBOX_MODES.join(' / ')},收到:${cfg.sandbox}`);
}
if (!/^[A-Za-z0-9._-]+$/.test(cfg.pm2Name)) {
throw new Error(`pm2Name 只能包含英數字、點、底線、連字號:${cfg.pm2Name}`);
}
if (cfg.web.enabled) {
const port = Number(cfg.web.port);
if (!Number.isInteger(port) || port < 1 || port > 65535) {
throw new Error(`web.port 必須是 1-65535 的整數:${cfg.web.port}`);
}
cfg.web.port = port;
}
// 速度政策固定 Fast:舊設定檔存了空值也矯正回 priority
if (!cfg.serviceTier) cfg.serviceTier = 'priority';
const minutes = Number(cfg.timeoutMinutes);
if (!(minutes > 0)) throw new Error(`timeoutMinutes 必須是正數:${cfg.timeoutMinutes}`);
cfg.timeoutMinutes = minutes;
return cfg;
}
// bot 執行前的額外檢查(web 檢視工具不需要 token,所以拆開)
function assertBotConfig(cfg) {
if (!cfg.telegramToken) {
throw new Error(
`缺少 Telegram token。請在 ${cfg.configPath} 設定 telegramToken` +
'或設環境變數 TGCODEX_TOKEN。(先跑 `telegram-codex-bot init` 產生設定檔)'
);
}
if (!fs.existsSync(cfg.workDir)) {
throw new Error(`workDir 不存在:${cfg.workDir}`);
}
}
function ensureStateDirs(cfg) {
for (const dir of [cfg.stateDir, cfg.logsDir, cfg.tmpDir]) {
fs.mkdirSync(dir, { recursive: true });
}
}
module.exports = { CONFIG_BASENAME, DEFAULTS, SANDBOX_MODES, loadConfig, assertBotConfig, ensureStateDirs, findConfigPath };