Files
2026-07-13 13:39:12 +08:00

176 lines
5.6 KiB
JavaScript

import { existsSync, readFileSync } from "node:fs";
import { createRequire } from "node:module";
import { dirname, isAbsolute, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const CLI_DIR = dirname(fileURLToPath(import.meta.url));
const DEFAULT_ROOT_DIR = join(CLI_DIR, "..", "..");
const require = createRequire(import.meta.url);
export const COMMON_PROVIDERS = [
{ id: "openai", name: "OpenAI" },
{ id: "anthropic", name: "Anthropic" },
{ id: "google", name: "Google AI" },
{ id: "openrouter", name: "OpenRouter" },
{ id: "groq", name: "Groq" },
{ id: "mistral", name: "Mistral" },
];
function normalizeCatalogCategory(exportName) {
const raw = exportName
.replace(/_PROVIDERS$/, "")
.toLowerCase()
.replaceAll("_", "-");
if (raw === "apikey") return "api-key";
return raw;
}
function loadTypeScript() {
try {
return require("typescript");
} catch {
return null;
}
}
function getPropertyName(ts, name) {
if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
return name.text;
}
return null;
}
function getObjectProperty(ts, objectLiteral, propertyName) {
return objectLiteral.properties.find(
(property) =>
ts.isPropertyAssignment(property) && getPropertyName(ts, property.name) === propertyName
);
}
function getStringProperty(ts, objectLiteral, propertyName) {
const property = getObjectProperty(ts, objectLiteral, propertyName);
const initializer = property?.initializer;
if (!initializer) return null;
if (ts.isStringLiteral(initializer) || ts.isNoSubstitutionTemplateLiteral(initializer)) {
return initializer.text;
}
return null;
}
function getBooleanProperty(ts, objectLiteral, propertyName) {
const property = getObjectProperty(ts, objectLiteral, propertyName);
const initializer = property?.initializer;
return initializer?.kind === ts.SyntaxKind.TrueKeyword;
}
function extractProviderBlocks(source, filePath) {
const ts = loadTypeScript();
if (!ts) return [];
const providers = [];
const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true);
sourceFile.forEachChild((node) => {
if (!ts.isVariableStatement(node)) return;
for (const declaration of node.declarationList.declarations) {
if (!ts.isIdentifier(declaration.name)) continue;
const exportName = declaration.name.text;
if (!exportName.endsWith("_PROVIDERS")) continue;
if (!declaration.initializer || !ts.isObjectLiteralExpression(declaration.initializer)) {
continue;
}
const category = normalizeCatalogCategory(exportName);
for (const property of declaration.initializer.properties) {
if (!ts.isPropertyAssignment(property)) continue;
if (!ts.isObjectLiteralExpression(property.initializer)) continue;
const key = getPropertyName(ts, property.name);
if (!key) continue;
const id = getStringProperty(ts, property.initializer, "id") || key;
const name = getStringProperty(ts, property.initializer, "name") || id;
providers.push({
id,
name,
category,
alias: getStringProperty(ts, property.initializer, "alias"),
website: getStringProperty(ts, property.initializer, "website"),
deprecated: getBooleanProperty(ts, property.initializer, "deprecated"),
hasFree: getBooleanProperty(ts, property.initializer, "hasFree"),
passthroughModels: getBooleanProperty(ts, property.initializer, "passthroughModels"),
});
}
}
});
return providers;
}
function fallbackAvailableProviders() {
return COMMON_PROVIDERS.map((provider) => ({
...provider,
category: "api-key",
alias: null,
website: null,
deprecated: false,
hasFree: false,
passthroughModels: false,
}));
}
function resolveProviderCatalogPath(rootDir, options = {}) {
const configuredPath = options.catalogPath || process.env.OMNIROUTE_PROVIDER_CATALOG_PATH;
if (configuredPath) {
return isAbsolute(configuredPath) ? configuredPath : resolve(rootDir, configuredPath);
}
return join(rootDir, "src", "shared", "constants", "providers.ts");
}
export function loadAvailableProviders(options = {}) {
const rootDir = typeof options === "string" ? options : options.rootDir || DEFAULT_ROOT_DIR;
const providersPath = resolveProviderCatalogPath(rootDir, options);
if (!existsSync(providersPath)) {
return fallbackAvailableProviders();
}
try {
const source = readFileSync(providersPath, "utf-8");
const providers = extractProviderBlocks(source, providersPath);
if (providers.length === 0) return fallbackAvailableProviders();
const seen = new Set();
return providers.filter((provider) => {
if (seen.has(provider.id)) return false;
seen.add(provider.id);
return true;
});
} catch {
return fallbackAvailableProviders();
}
}
export function getAvailableProviderCategories(providers = loadAvailableProviders()) {
return [...new Set(providers.map((provider) => provider.category))].sort();
}
export function getProviderDisplayName(providerId) {
return COMMON_PROVIDERS.find((provider) => provider.id === providerId)?.name || providerId;
}
export function formatProviderChoices() {
return COMMON_PROVIDERS.map((provider, index) => `${index + 1}. ${provider.name}`).join("\n");
}
export function resolveProviderChoice(value) {
const trimmed = String(value || "").trim();
const numeric = Number.parseInt(trimmed, 10);
if (Number.isInteger(numeric) && numeric >= 1 && numeric <= COMMON_PROVIDERS.length) {
return COMMON_PROVIDERS[numeric - 1].id;
}
return trimmed || "openai";
}