docs(ai-code-review): 補齊各模組 JSDoc、指令檔逐行註解並重建 README
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
303104bb20
commit
1378f03595
+61
-10
@@ -4,6 +4,12 @@ import { warn } from './log.js';
|
||||
/** 本次執行的 token 累計(跨所有 LLM 呼叫)。 */
|
||||
const runUsage = { calls: 0, promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
||||
|
||||
/**
|
||||
* 安全數字轉換:將任意輸入轉為有限數字,無法轉換或非有限值(NaN/Infinity)一律回 0。
|
||||
* 常用於正規化外部 API 回應或 HTTP header 取出的值,避免污染後續加總與百分比運算。
|
||||
* @param {*} x 任意待轉換的值。
|
||||
* @returns {number} 有限數字;否則為 0。
|
||||
*/
|
||||
function num(x) {
|
||||
const n = Number(x);
|
||||
return Number.isFinite(n) ? n : 0;
|
||||
@@ -83,7 +89,12 @@ export function resetRunUsage() {
|
||||
/** 最近一次回應的速率配額(rate limit)快照,用來計算「當前視窗剩餘百分比」。 */
|
||||
const rateLimit = { hasData: false, remaining: null, limit: null, kind: null };
|
||||
|
||||
/** 將物件的 key 全部轉小寫,方便對大小寫不敏感的 HTTP header 取值。 */
|
||||
/**
|
||||
* 將物件第一層的 key 全部轉為小寫並回傳新物件,方便對大小寫不敏感的 HTTP header 取值。
|
||||
* 不修改傳入物件;僅處理第一層 key。呼叫端須自行確保傳入為物件。
|
||||
* @param {Object<string, *>} obj 來源物件(通常為 HTTP response headers)。
|
||||
* @returns {Object<string, *>} key 全小寫的新物件。
|
||||
*/
|
||||
function lowerCaseKeys(obj) {
|
||||
const out = {};
|
||||
for (const k of Object.keys(obj)) out[k.toLowerCase()] = obj[k];
|
||||
@@ -128,12 +139,20 @@ export function resetRateLimit() {
|
||||
rateLimit.kind = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 去除字串結尾的單一斜線(常用於正規化 baseURL 以利串接路徑)。
|
||||
* 空值會被視為空字串;僅移除最後一個斜線,不處理連續尾斜線。
|
||||
* @param {*} s 來源字串(通常為 URL)。
|
||||
* @returns {string} 去除結尾斜線後的字串。
|
||||
*/
|
||||
const stripSlash = (s) => String(s || '').replace(/\/$/, '');
|
||||
|
||||
/**
|
||||
* 以實際 hostname 精確比對是否為 OpenRouter(僅接受 apex 域名 `openrouter.ai`),
|
||||
* 避免被偽造的 baseURL(如 `openrouter.ai.evil.com`、`evil.com/openrouter.ai` 或任何子網域)
|
||||
* 矇騙而把 API key 送往非 OpenRouter 主機。
|
||||
* 以解析後的 hostname 精確比對 baseURL 是否為 OpenRouter(僅接受 apex 域名 openrouter.ai)。
|
||||
* 用於 fetchAccountQuota 的安全守門:防止被偽造或子網域 baseURL 矇騙而外洩 API key。
|
||||
* 無法解析為合法 URL 時回 false(不丟例外)。
|
||||
* @param {string} baseURL 待驗證的 base URL。
|
||||
* @returns {boolean} hostname 恰為 openrouter.ai 時為 true,否則 false。
|
||||
*/
|
||||
function isOpenRouterBaseURL(baseURL) {
|
||||
try {
|
||||
@@ -144,8 +163,14 @@ function isOpenRouterBaseURL(baseURL) {
|
||||
}
|
||||
|
||||
/**
|
||||
* OpenRouter:以 API key 呼叫 GET /auth/key 取得額度(可靠)。
|
||||
* 回傳金額單位為 USD credits。
|
||||
* 呼叫 OpenRouter GET /auth/key,以 API key 取得帳號額度(單位 USD credits)。
|
||||
* remaining 缺漏時以 limit - used 推算;limit 為 null(無上限)時 remaining 亦為 null。
|
||||
* 本函式不攔截例外;HTTP/網路錯誤會向上拋出,由呼叫端負責降級。
|
||||
* @param {{apiKey:string, baseURL:string}} cfg 連線設定(API key 與 base URL)。
|
||||
* @param {function(string, object): Promise<{data:*}>} get HTTP GET 函式(可注入,預設 axios.get)。
|
||||
* @returns {Promise<{available:true, used:number, limit:number|null, remaining:number|null, currency:'USD', source:'openrouter'}>}
|
||||
* 額度資訊。
|
||||
* @throws {Error} HTTP 請求失敗(逾時、非 2xx、網路錯誤等)時拋出。
|
||||
*/
|
||||
async function fetchOpenRouterQuota({ apiKey, baseURL }, get) {
|
||||
const resp = await get(`${stripSlash(baseURL)}/auth/key`, {
|
||||
@@ -193,7 +218,11 @@ export async function fetchAccountQuota(provider, config = {}, deps = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
/** 千分位整數/小數格式。 */
|
||||
/**
|
||||
* 將數字格式化為千分位字串(保留原有小數)。null/undefined/NaN 一律回 '0'。
|
||||
* @param {number|string|null|undefined} n 待格式化的數值。
|
||||
* @returns {string} 千分位字串,例如 1234567 → '1,234,567'。
|
||||
*/
|
||||
function fmt(n) {
|
||||
if (n == null || Number.isNaN(Number(n))) return '0';
|
||||
const [int, frac] = String(Number(n)).split('.');
|
||||
@@ -201,10 +230,22 @@ function fmt(n) {
|
||||
return frac ? `${withCommas}.${frac}` : withCommas;
|
||||
}
|
||||
|
||||
/**
|
||||
* 將數值格式化為金額字串:有幣別時前綴幣別(如 'USD 1,234'),無幣別則只回千分位數字。
|
||||
* @param {string} currency 幣別代碼(空字串/falsy 表示無幣別)。
|
||||
* @param {number|string|null|undefined} n 數值。
|
||||
* @returns {string} 格式化後的金額字串。
|
||||
*/
|
||||
function money(currency, n) {
|
||||
return currency ? `${currency} ${fmt(n)}` : fmt(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* 四捨五入到小數一位(用於百分比顯示)。
|
||||
* 不對非數字防呆;非有限輸入會得到 NaN(呼叫端應先確保為有限數)。
|
||||
* @param {number|string} n 數值。
|
||||
* @returns {number} 四捨五入到一位小數的結果。
|
||||
*/
|
||||
function round1(n) {
|
||||
return Math.round(Number(n) * 10) / 10;
|
||||
}
|
||||
@@ -212,9 +253,12 @@ function round1(n) {
|
||||
const RATE_KIND_LABEL = { tokens: 'token', requests: '次數' };
|
||||
|
||||
/**
|
||||
* 計算「剩餘百分比」= remaining / limit × 100。
|
||||
* limit 或 remaining 為 null/undefined/NaN/Infinity,或 limit ≤ 0 時回 null,
|
||||
* 避免算出 Infinity%/NaN%/負百分比或除以零。
|
||||
* 計算剩餘百分比(remaining / limit × 100),四捨五入到一位小數。
|
||||
* 任一參數為 null/undefined/NaN/Infinity,或 limit ≤ 0 時回 null,
|
||||
* 以避免算出 Infinity%、NaN%、負百分比或除以零。
|
||||
* @param {number|null|undefined} remaining 剩餘量。
|
||||
* @param {number|null|undefined} limit 上限。
|
||||
* @returns {number|null} 百分比(一位小數);無法計算時為 null。
|
||||
*/
|
||||
function calculatePercent(remaining, limit) {
|
||||
if (remaining == null || limit == null) return null;
|
||||
@@ -253,6 +297,13 @@ export function resolveRemainingPercent(quota, rate) {
|
||||
return { percent: null, reason };
|
||||
}
|
||||
|
||||
/**
|
||||
* 把 resolveRemainingPercent 的結果格式化為一行 Markdown 文字(剩餘可用百分比與明細)。
|
||||
* 無法計算時輸出帶原因的說明字串。
|
||||
* @param {{percent:number, basis:string, remaining:number, limit:number, unit:string}|{percent:null, reason:string}} pct
|
||||
* resolveRemainingPercent 的回傳值。
|
||||
* @returns {string} 單行 Markdown 字串。
|
||||
*/
|
||||
function remainingLine(pct) {
|
||||
if (pct.percent == null) return `剩餘可用:無法計算百分比(${pct.reason})`;
|
||||
const detail = `${pct.basis}:${money(pct.unit, pct.remaining)} / ${money(pct.unit, pct.limit)}`;
|
||||
|
||||
Reference in New Issue
Block a user