/** * Script: đọc providersDisplay.js + providers.js, inject display+category+uiAlias+extra vào từng registry file. * Chạy: node scripts/injectDisplayToRegistry.mjs */ import fs from "fs"; import path from "path"; import { fileURLToPath } from "url"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const ROOT = path.resolve(__dirname, ".."); const REGISTRY_DIR = path.join(ROOT, "open-sse/providers/registry"); // ── 1. Build DISPLAY map từ providersDisplay.js (parse thủ công để không cần import) ── // Đọc file, eval trong sandbox đơn giản const displaySrc = fs.readFileSync(path.join(ROOT, "src/shared/constants/providersDisplay.js"), "utf8"); const RISK_NOTICE = "⚠️ Risk Notice: This provider uses a subscription/OAuth session not officially licensed for proxy/router use. Account may be restricted or banned. Use at your own risk."; // strip export keywords + inject RISK_NOTICE as param so no redeclaration const displayBody = displaySrc .replace(/^export const /gm, "const ") .replace(/^export function /gm, "function ") .replace(/^const RISK_NOTICE\s*=.*$/m, ""); // remove redeclaration // eslint-disable-next-line no-new-func const getDisplay = new Function("RISK_NOTICE", `${displayBody}; return PROVIDER_DISPLAY;`); const DISPLAY = getDisplay(RISK_NOTICE); // ── 2. Build CATEGORY + EXTRA map từ providers.js ── // Map: providerId → { category, uiAlias, extra fields } const CATEGORY_MAP = {}; // Đọc providers.js source để extract thủ công từng dòng const provSrc = fs.readFileSync(path.join(ROOT, "src/shared/constants/providers.js"), "utf8"); // Detect category blocks const CATEGORIES = { free: /export const FREE_PROVIDERS\s*=\s*\{([\s\S]*?)\n\};/, freeTier: /export const FREE_TIER_PROVIDERS\s*=\s*\{([\s\S]*?)\n\};/, oauth: /export const OAUTH_PROVIDERS\s*=\s*\{([\s\S]*?)\n\};/, apikey: /export const APIKEY_PROVIDERS\s*=\s*\{([\s\S]*?)\n\};/, webCookie: /export const WEB_COOKIE_PROVIDERS\s*=\s*\{([\s\S]*?)\n\};/, }; // Extract provider ids + uiAlias + extra fields per category // Parse dòng dạng: " openai: { ...D("openai"), id: "openai", alias: "openai", ... }" const ENTRY_RE = /^\s{2}["']?([\w-]+)["']?\s*:\s*\{[^}]*?id:\s*["']([\w-]+)["'][^}]*?alias:\s*["']([\w-]+)["']([\s\S]*?)(?=\n\s{2}["']?[\w-]|\n\};)/gm; // Extra fields cần lấy từ providers.js (không lấy display, id, alias vì đã có nguồn khác) const EXTRA_FIELDS = [ "thinkingConfig", "regions", "defaultRegion", "hasProviderSpecificData", "authType", "authHint", "passthroughModels", "noAuth", "hiddenKinds", "hasOAuth", "authModes", ]; // THINKING_CONFIG values để inline const THINKING_CONFIG = { extended: { options: ["auto", "on", "off"], defaultMode: "auto", defaultBudgetTokens: 10000 }, effort: { options: ["auto", "none", "low", "medium", "high"], defaultMode: "auto" }, }; // Parse thủ công từng category block for (const [cat, re] of Object.entries(CATEGORIES)) { const match = provSrc.match(re); if (!match) continue; const block = match[1]; // Tìm tất cả entry lines (không comment) const lines = block.split("\n").filter(l => l.trim() && !l.trim().startsWith("//")); for (const line of lines) { // Extract id từ id: "xxx" const idM = line.match(/\bid:\s*["']([\w-]+)["']/); // Extract uiAlias từ alias: "xxx" const aliasM = line.match(/\balias:\s*["']([\w-]+)["']/); if (!idM) continue; const id = idM[1]; const uiAlias = aliasM ? aliasM[1] : id; const extra = {}; // thinkingConfig if (line.includes("THINKING_CONFIG.effort")) extra.thinkingConfig = THINKING_CONFIG.effort; else if (line.includes("THINKING_CONFIG.extended")) extra.thinkingConfig = THINKING_CONFIG.extended; // hasProviderSpecificData if (line.includes("hasProviderSpecificData: true")) extra.hasProviderSpecificData = true; // hasOAuth if (line.includes("hasOAuth: true")) extra.hasOAuth = true; // authModes const authModesM = line.match(/authModes:\s*(\[[^\]]+\])/); if (authModesM) { try { extra.authModes = JSON.parse(authModesM[1].replace(/'/g, '"')); } catch {} } // authType (webCookie) const authTypeM = line.match(/authType:\s*["']([\w-]+)["']/); if (authTypeM) extra.authType = authTypeM[1]; // authHint const authHintM = line.match(/authHint:\s*["']([^"']+)["']/); if (authHintM) extra.authHint = authHintM[1]; // noAuth if (line.includes("noAuth: true")) extra.noAuth = true; // passthroughModels if (line.includes("passthroughModels: true")) extra.passthroughModels = true; // hiddenKinds const hiddenKindsM = line.match(/hiddenKinds:\s*(\[[^\]]+\])/); if (hiddenKindsM) { try { extra.hiddenKinds = JSON.parse(hiddenKindsM[1].replace(/'/g, '"')); } catch {} } // regions (xiaomi-tokenplan) const regionsM = line.match(/regions:\s*(\[[\s\S]*?\])/); if (regionsM) { try { extra.regions = JSON.parse(regionsM[1].replace(/'/g, '"')); } catch {} } const defRegionM = line.match(/defaultRegion:\s*["']([\w-]+)["']/); if (defRegionM) extra.defaultRegion = defRegionM[1]; CATEGORY_MAP[id] = { category: cat, uiAlias, extra }; } } // ── 3. Inject vào từng registry file ── const registryFiles = fs.readdirSync(REGISTRY_DIR) .filter(f => f.endsWith(".js") && f !== "index.js") .map(f => f.replace(".js", "")); let injected = 0; let skipped = 0; const results = []; for (const id of registryFiles) { const filePath = path.join(REGISTRY_DIR, `${id}.js`); let src = fs.readFileSync(filePath, "utf8"); // Bỏ qua nếu đã có display field if (src.includes("display:")) { skipped++; results.push(`⏭️ ${id} (already has display)`); continue; } const display = DISPLAY[id]; const catInfo = CATEGORY_MAP[id]; if (!display && !catInfo) { skipped++; results.push(`⚠️ ${id} (no display + no category data)`); continue; } // Build display block let displayBlock = ""; if (display) { const d = { ...display }; // Thay RISK_NOTICE string về const reference khi serialize const RISK = RISK_NOTICE; const displayJson = JSON.stringify(d, null, 4) .replace(new RegExp(JSON.stringify(RISK).slice(1, -1), "g"), "RISK_NOTICE"); displayBlock = ` display: ${displayJson.replace(/^/gm, " ").trimStart()},\n`; } // Build category line const categoryLine = catInfo ? ` category: "${catInfo.category}",\n` : ""; // Build uiAlias line (chỉ khi khác với alias routing) let uiAliasLine = ""; if (catInfo && catInfo.uiAlias && catInfo.uiAlias !== id) { uiAliasLine = ` uiAlias: "${catInfo.uiAlias}",\n`; } // Build extra fields let extraBlock = ""; if (catInfo && Object.keys(catInfo.extra).length > 0) { for (const [k, v] of Object.entries(catInfo.extra)) { extraBlock += ` ${k}: ${JSON.stringify(v)},\n`; } } // Inject SAU dòng "alias:" hoặc cuối object (trước closing "};") const insertBlock = displayBlock + categoryLine + uiAliasLine + extraBlock; if (!insertBlock.trim()) { skipped++; results.push(`⏭️ ${id} (nothing to inject)`); continue; } // Tìm vị trí sau field "alias:" để inject const aliasLineRe = /^(\s+"?alias"?\s*:\s*["'][^"']+["'],?\n)/m; if (aliasLineRe.test(src)) { src = src.replace(aliasLineRe, `$1${insertBlock}`); } else { // Fallback: inject trước closing "};" src = src.replace(/^(\}\s*;\s*)$/m, `${insertBlock}$1`); } // Thêm RISK_NOTICE import nếu cần if (insertBlock.includes("RISK_NOTICE") && !src.includes("RISK_NOTICE")) { const riskLine = `const RISK_NOTICE = ${JSON.stringify(RISK_NOTICE)};\n\n`; src = riskLine + src; } fs.writeFileSync(filePath, src); injected++; results.push(`✅ ${id}`); } console.log(`\n📦 Inject display+category vào registry files:`); for (const r of results) console.log(` ${r}`); console.log(`\n✅ Injected: ${injected} | ⏭️ Skipped: ${skipped}`);