| #!/usr/bin/env node
|
|
|
|
|
|
|
| import fs from "node:fs";
|
| import path from "node:path";
|
| import { fileURLToPath } from "node:url";
|
| import {
|
| FREE_PROVIDERS,
|
| OAUTH_PROVIDERS,
|
| WEB_COOKIE_PROVIDERS,
|
| APIKEY_PROVIDERS,
|
| LOCAL_PROVIDERS,
|
| SEARCH_PROVIDERS,
|
| AUDIO_ONLY_PROVIDERS,
|
| UPSTREAM_PROXY_PROVIDERS,
|
| CLOUD_AGENT_PROVIDERS,
|
| SYSTEM_PROVIDERS,
|
| IMAGE_ONLY_PROVIDER_IDS,
|
| AGGREGATOR_PROVIDER_IDS,
|
| ENTERPRISE_CLOUD_PROVIDER_IDS,
|
| VIDEO_PROVIDER_IDS,
|
| EMBEDDING_RERANK_PROVIDER_IDS,
|
| SELF_HOSTED_CHAT_PROVIDER_IDS,
|
| } from "../../src/shared/constants/providers.ts";
|
|
|
| const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
| const ROOT = path.resolve(__dirname, "..", "..");
|
| const OUT_FILE = path.join(ROOT, "docs", "reference", "PROVIDER_REFERENCE.md");
|
|
|
| type ProviderRecord = {
|
| id: string;
|
| alias?: string | undefined;
|
| name: string;
|
| icon?: string;
|
| color?: string;
|
| textIcon?: string;
|
| website?: string;
|
| authHint?: string;
|
| freeNote?: string;
|
| hasFree?: boolean;
|
| deprecated?: boolean;
|
| deprecationReason?: string;
|
| [k: string]: unknown;
|
| };
|
|
|
| function asRecords(map: Record<string, ProviderRecord>): ProviderRecord[] {
|
| return Object.values(map).map((p) => ({ ...p }));
|
| }
|
|
|
| function escapeCell(value: string | undefined): string {
|
| if (!value) return "β";
|
|
|
| return value.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\n/g, " ");
|
| }
|
|
|
| function row(p: ProviderRecord, category: string): string {
|
| const alias = p.alias ? `\`${p.alias}\`` : "β";
|
| const hint = p.deprecated
|
| ? `β οΈ **DEPRECATED.** ${escapeCell(p.deprecationReason)}`
|
| : escapeCell(p.authHint || p.freeNote);
|
| const link = p.website ? `[link](${p.website})` : "β";
|
| return `| \`${p.id}\` | ${alias} | ${escapeCell(p.name)} | ${category} | ${link} | ${hint} |`;
|
| }
|
|
|
| function categoryTags(id: string): string[] {
|
| const tags: string[] = [];
|
| if (IMAGE_ONLY_PROVIDER_IDS.has(id)) tags.push("image");
|
| if (VIDEO_PROVIDER_IDS.has(id)) tags.push("video");
|
| if (AGGREGATOR_PROVIDER_IDS.has(id)) tags.push("aggregator");
|
| if (ENTERPRISE_CLOUD_PROVIDER_IDS.has(id)) tags.push("enterprise");
|
| if (EMBEDDING_RERANK_PROVIDER_IDS.has(id)) tags.push("embed/rerank");
|
| if (SELF_HOSTED_CHAT_PROVIDER_IDS.has(id)) tags.push("self-hosted");
|
| return tags;
|
| }
|
|
|
| function sortById(rows: ProviderRecord[]): ProviderRecord[] {
|
| return [...rows].sort((a, b) => a.id.localeCompare(b.id));
|
| }
|
|
|
| function buildSection(title: string, rows: ProviderRecord[], category: string): string {
|
| if (rows.length === 0) return "";
|
| const lines: string[] = [];
|
| lines.push(`## ${title} (${rows.length})\n`);
|
| lines.push("| ID | Alias | Name | Tags | Website | Notes |");
|
| lines.push("|----|-------|------|------|---------|-------|");
|
| for (const p of sortById(rows)) {
|
| const tags = [category, ...categoryTags(p.id)].join(", ");
|
| lines.push(row(p, tags));
|
| }
|
| lines.push("");
|
| return lines.join("\n");
|
| }
|
|
|
| function buildHeader(total: number): string {
|
| const date = new Date().toISOString().slice(0, 10);
|
| const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, "package.json"), "utf8")) as {
|
| version?: string;
|
| };
|
| return [
|
| "---",
|
| 'title: "Provider Reference"',
|
| `version: ${pkg.version || "unknown"}`,
|
| `lastUpdated: ${date}`,
|
| "---",
|
| "",
|
| "# Provider Reference",
|
| "",
|
| `> **Auto-generated** from \`src/shared/constants/providers.ts\` β do not edit by hand.`,
|
| `> Regenerate with: \`npm run gen:provider-reference\``,
|
| `> **Last generated:** ${date}`,
|
| "",
|
| `Total providers: **${total}**. See category breakdown below.`,
|
| "",
|
| "## Categories",
|
| "",
|
| "- **Free** β free tier with API key (configured via dashboard)",
|
| "- **OAuth** β sign-in flow handled by OmniRoute, no API key needed",
|
| "- **Web cookie** β wraps the provider's web app via cookie auth",
|
| "- **API key** β paid provider configured via API key (free credits may apply)",
|
| "- **Local** β runs on the user's machine (Ollama, LM Studio, vLLM, etc.)",
|
| "- **Search** β web search providers",
|
| "- **Audio** β audio-only providers (TTS/STT)",
|
| "- **Upstream proxy** β providers that proxy to other providers",
|
| "- **Cloud agent** β long-running coding agents (Codex Cloud, Devin, Jules)",
|
| "- **System** β OmniRoute-internal providers (loopback, etc.)",
|
| "",
|
| "Additional tags: `image`, `video`, `aggregator`, `enterprise`, `embed/rerank`, `self-hosted`.",
|
| "",
|
| "Use the dashboard at `/dashboard/providers` to enable, configure, and test each provider.",
|
| "",
|
| "---",
|
| "",
|
| ].join("\n");
|
| }
|
|
|
| function main() {
|
| const free = asRecords(FREE_PROVIDERS);
|
| const oauth = asRecords(OAUTH_PROVIDERS);
|
| const webCookie = asRecords(WEB_COOKIE_PROVIDERS);
|
| const apiKey = asRecords(APIKEY_PROVIDERS);
|
| const local = asRecords(LOCAL_PROVIDERS);
|
| const search = asRecords(SEARCH_PROVIDERS);
|
| const audio = asRecords(AUDIO_ONLY_PROVIDERS);
|
| const upstreamProxy = asRecords(UPSTREAM_PROXY_PROVIDERS);
|
| const cloudAgent = asRecords(CLOUD_AGENT_PROVIDERS);
|
| const system = asRecords(SYSTEM_PROVIDERS);
|
|
|
| const allIds = new Set<string>([
|
| ...free.map((p) => p.id),
|
| ...oauth.map((p) => p.id),
|
| ...webCookie.map((p) => p.id),
|
| ...apiKey.map((p) => p.id),
|
| ...local.map((p) => p.id),
|
| ...search.map((p) => p.id),
|
| ...audio.map((p) => p.id),
|
| ...upstreamProxy.map((p) => p.id),
|
| ...cloudAgent.map((p) => p.id),
|
| ...system.map((p) => p.id),
|
| ]);
|
|
|
| const sections = [
|
| buildSection("Free Tier (OAuth-first or no-key)", free, "Free"),
|
| buildSection("OAuth Providers", oauth, "OAuth"),
|
| buildSection("Web Cookie Providers", webCookie, "Web cookie"),
|
| buildSection("API Key Providers (paid / paid-with-free-credits)", apiKey, "API key"),
|
| buildSection("Local Providers", local, "Local"),
|
| buildSection("Search Providers", search, "Search"),
|
| buildSection("Audio-only Providers", audio, "Audio"),
|
| buildSection("Upstream Proxy Providers", upstreamProxy, "Upstream proxy"),
|
| buildSection("Cloud Agent Providers", cloudAgent, "Cloud agent"),
|
| buildSection("System Providers", system, "System"),
|
| ];
|
|
|
| const footer = [
|
| "## Sources of truth",
|
| "",
|
| "- Catalog: [`src/shared/constants/providers.ts`](../../src/shared/constants/providers.ts)",
|
| "- Registry (per-model details): [`open-sse/config/providerRegistry.ts`](../../open-sse/config/providerRegistry.ts)",
|
| "- Executors: [`open-sse/executors/`](../../open-sse/executors/) (31 files)",
|
| "- Translators: [`open-sse/translator/`](../../open-sse/translator/)",
|
| "",
|
| "## See Also",
|
| "",
|
| "- [FREE_TIERS.md](./FREE_TIERS.md) β curated free-tier guide",
|
| "- [USER_GUIDE.md](../guides/USER_GUIDE.md) β provider setup walkthrough",
|
| "- [ARCHITECTURE.md](../architecture/ARCHITECTURE.md) β overall architecture",
|
| "",
|
| ].join("\n");
|
|
|
| const content = buildHeader(allIds.size) + sections.join("\n") + "\n" + footer;
|
| fs.writeFileSync(OUT_FILE, content);
|
| console.log(`β Wrote ${OUT_FILE}`);
|
| console.log(` Providers: ${allIds.size} unique IDs`);
|
| console.log(
|
| ` Sections: free=${free.length}, oauth=${oauth.length}, web=${webCookie.length}, ` +
|
| `apikey=${apiKey.length}, local=${local.length}, search=${search.length}, ` +
|
| `audio=${audio.length}, proxy=${upstreamProxy.length}, cloud=${cloudAgent.length}, system=${system.length}`
|
| );
|
| }
|
|
|
| main();
|
|
|