refactor(open-sse): registry consolidation + DRY media/oauth/adhoc cleanup
- Single-source registry: oauth clientId/tokenUrl, usage URLs, image/embed configs, search defaultModel, codex fixedPort, google token url derive. - Remove 29 unused OmniRoute providers (registry 100→71); media intact. - De-adhoc: codex literals → registry format/oauth flags; reasoningInject, image/embed openrouter headers + xai bodyFields config-driven. - Add REGISTRY_TEMPLATE.js + expand PROVIDER_DEFAULTS/schema JSDoc. - Baselines updated; PROVIDERS 62 + alias 90 byte-for-byte, golden snapshots. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,143 +1,22 @@
|
||||
import { OAUTH_ALIASES } from "../config/providerModels.js";
|
||||
import REGISTRY from "../providers/registry/index.js";
|
||||
|
||||
// Reverse OAuth aliases (id→alias) into alias→id; "mmf" intentionally excluded to preserve behavior
|
||||
const OAUTH_ALIAS_TO_ID = Object.fromEntries(
|
||||
Object.entries(OAUTH_ALIASES)
|
||||
.filter(([id]) => id !== "mimo-free")
|
||||
.map(([id, alias]) => [alias, id])
|
||||
);
|
||||
|
||||
// Provider alias to ID mapping (OAuth short aliases derived above)
|
||||
const ALIAS_TO_PROVIDER_ID = {
|
||||
...OAUTH_ALIAS_TO_ID,
|
||||
ocg: "opencode-go",
|
||||
qoder: "qoder",
|
||||
// TTS providers
|
||||
// Alias→id derived from registry single-source: id→id, alias→id, aliases[]→id.
|
||||
// Media-only providers without a registry transport entry keep explicit aliases here.
|
||||
const MEDIA_ONLY_ALIASES = {
|
||||
el: "elevenlabs",
|
||||
// API Key providers
|
||||
openai: "openai",
|
||||
vercel: "vercel-ai-gateway",
|
||||
"vercel-ai-gateway": "vercel-ai-gateway",
|
||||
anthropic: "anthropic",
|
||||
gemini: "gemini",
|
||||
openrouter: "openrouter",
|
||||
glm: "glm",
|
||||
kimi: "kimi",
|
||||
minimax: "minimax",
|
||||
"minimax-cn": "minimax-cn",
|
||||
hf: "huggingface",
|
||||
huggingface: "huggingface",
|
||||
ds: "deepseek",
|
||||
deepseek: "deepseek",
|
||||
cmc: "commandcode",
|
||||
commandcode: "commandcode",
|
||||
groq: "groq",
|
||||
xai: "xai",
|
||||
mistral: "mistral",
|
||||
pplx: "perplexity",
|
||||
perplexity: "perplexity",
|
||||
together: "together",
|
||||
fireworks: "fireworks",
|
||||
cerebras: "cerebras",
|
||||
cohere: "cohere",
|
||||
nvidia: "nvidia",
|
||||
nebius: "nebius",
|
||||
siliconflow: "siliconflow",
|
||||
hyp: "hyperbolic",
|
||||
hyperbolic: "hyperbolic",
|
||||
dg: "deepgram",
|
||||
deepgram: "deepgram",
|
||||
aai: "assemblyai",
|
||||
assemblyai: "assemblyai",
|
||||
nb: "nanobanana",
|
||||
nanobanana: "nanobanana",
|
||||
ch: "chutes",
|
||||
chutes: "chutes",
|
||||
ark: "volcengine-ark",
|
||||
"volcengine-ark": "volcengine-ark",
|
||||
byteplus: "byteplus",
|
||||
bpm: "byteplus",
|
||||
cursor: "cursor",
|
||||
vx: "vertex",
|
||||
vertex: "vertex",
|
||||
vxp: "vertex-partner",
|
||||
"vertex-partner": "vertex-partner",
|
||||
// Web cookie providers
|
||||
gw: "grok-web",
|
||||
"grok-web": "grok-web",
|
||||
pw: "perplexity-web",
|
||||
"perplexity-web": "perplexity-web",
|
||||
mimo: "xiaomi-mimo",
|
||||
"xiaomi-mimo": "xiaomi-mimo",
|
||||
xmtp: "xiaomi-tokenplan",
|
||||
"xiaomi-tokenplan": "xiaomi-tokenplan",
|
||||
cf: "cloudflare-ai",
|
||||
"cloudflare-ai": "cloudflare-ai",
|
||||
// Image/video providers
|
||||
fal: "fal-ai",
|
||||
"fal-ai": "fal-ai",
|
||||
stability: "stability-ai",
|
||||
"stability-ai": "stability-ai",
|
||||
bfl: "black-forest-labs",
|
||||
"black-forest-labs": "black-forest-labs",
|
||||
recraft: "recraft",
|
||||
topaz: "topaz",
|
||||
runway: "runwayml",
|
||||
runwayml: "runwayml",
|
||||
// Embedding/rerank
|
||||
jina: "jina-ai",
|
||||
"jina-ai": "jina-ai",
|
||||
// TTS
|
||||
polly: "aws-polly",
|
||||
"aws-polly": "aws-polly",
|
||||
// Free-tier providers (synced from OmniRoute)
|
||||
agentrouter: "agentrouter",
|
||||
aimlapi: "aimlapi",
|
||||
aiml: "aimlapi",
|
||||
novita: "novita",
|
||||
modal: "modal",
|
||||
mdl: "modal",
|
||||
reka: "reka",
|
||||
nlpcloud: "nlpcloud",
|
||||
nlpc: "nlpcloud",
|
||||
bazaarlink: "bazaarlink",
|
||||
bzl: "bazaarlink",
|
||||
completions: "completions",
|
||||
cpl: "completions",
|
||||
enally: "enally",
|
||||
enly: "enally",
|
||||
freetheai: "freetheai",
|
||||
fta: "freetheai",
|
||||
llm7: "llm7",
|
||||
lepton: "lepton",
|
||||
kluster: "kluster",
|
||||
ai21: "ai21",
|
||||
"inference-net": "inference-net",
|
||||
inet: "inference-net",
|
||||
predibase: "predibase",
|
||||
bytez: "bytez",
|
||||
morph: "morph",
|
||||
longcat: "longcat",
|
||||
lc: "longcat",
|
||||
puter: "puter",
|
||||
pu: "puter",
|
||||
uncloseai: "uncloseai",
|
||||
unc: "uncloseai",
|
||||
scaleway: "scaleway",
|
||||
scw: "scaleway",
|
||||
deepinfra: "deepinfra",
|
||||
sambanova: "sambanova",
|
||||
samba: "sambanova",
|
||||
nscale: "nscale",
|
||||
baseten: "baseten",
|
||||
publicai: "publicai",
|
||||
"nous-research": "nous-research",
|
||||
nous: "nous-research",
|
||||
glhf: "glhf",
|
||||
bb: "blackbox",
|
||||
blackbox: "blackbox",
|
||||
};
|
||||
|
||||
const ALIAS_TO_PROVIDER_ID = { ...MEDIA_ONLY_ALIASES };
|
||||
for (const entry of REGISTRY) {
|
||||
ALIAS_TO_PROVIDER_ID[entry.id] = entry.id;
|
||||
if (entry.alias) ALIAS_TO_PROVIDER_ID[entry.alias] = entry.id;
|
||||
for (const a of entry.aliases || []) ALIAS_TO_PROVIDER_ID[a] = entry.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve provider alias to provider ID
|
||||
*/
|
||||
@@ -237,6 +116,15 @@ export async function getModelInfoCore(modelStr, aliasesOrGetter) {
|
||||
};
|
||||
}
|
||||
|
||||
// Config-driven prefix → provider inference (first match wins, fallback "openai").
|
||||
const MODEL_PREFIX_PROVIDERS = [
|
||||
[/^claude-/, "anthropic"],
|
||||
[/^gemini-/, "gemini"],
|
||||
[/^gpt-/, "openai"],
|
||||
[/^o[134]/, "openai"],
|
||||
[/^deepseek-/, "openrouter"],
|
||||
];
|
||||
|
||||
/**
|
||||
* Infer provider from model name prefix
|
||||
* Used as fallback when no provider prefix or alias is given
|
||||
@@ -244,12 +132,5 @@ export async function getModelInfoCore(modelStr, aliasesOrGetter) {
|
||||
function inferProviderFromModelName(modelName) {
|
||||
if (!modelName) return "openai";
|
||||
const m = modelName.toLowerCase();
|
||||
if (m.startsWith("claude-")) return "anthropic";
|
||||
if (m.startsWith("gemini-")) return "gemini";
|
||||
if (m.startsWith("gpt-")) return "openai";
|
||||
if (m.startsWith("o1") || m.startsWith("o3") || m.startsWith("o4"))
|
||||
return "openai";
|
||||
if (m.startsWith("deepseek-")) return "openrouter";
|
||||
// Default fallback
|
||||
return "openai";
|
||||
return MODEL_PREFIX_PROVIDERS.find(([re]) => re.test(m))?.[1] || "openai";
|
||||
}
|
||||
|
||||
@@ -3,8 +3,10 @@ import {
|
||||
isUnrecoverableRefreshError,
|
||||
refreshTokenByProvider,
|
||||
} from "./tokenRefresh.js";
|
||||
import { PROVIDER_OAUTH } from "../providers/index.js";
|
||||
|
||||
export const CODEX_MAX_REFRESH_AGE_MS = 8 * 24 * 60 * 60 * 1000;
|
||||
// Single source: codex.oauth.maxRefreshAgeMs (8 days) — proactive refresh window
|
||||
export const CODEX_MAX_REFRESH_AGE_MS = PROVIDER_OAUTH["codex"]?.maxRefreshAgeMs;
|
||||
|
||||
const refreshLocks = new Map();
|
||||
|
||||
@@ -35,9 +37,9 @@ export function getCredentialLastRefreshMs(credentials) {
|
||||
);
|
||||
}
|
||||
|
||||
export function isCodexRefreshStale(credentials, nowMs = Date.now()) {
|
||||
export function isCodexRefreshStale(credentials, nowMs = Date.now(), maxAgeMs = CODEX_MAX_REFRESH_AGE_MS) {
|
||||
const lastRefreshMs = getCredentialLastRefreshMs(credentials);
|
||||
return !lastRefreshMs || nowMs - lastRefreshMs >= CODEX_MAX_REFRESH_AGE_MS;
|
||||
return !lastRefreshMs || nowMs - lastRefreshMs >= maxAgeMs;
|
||||
}
|
||||
|
||||
export function shouldRefreshCredentials(provider, credentials, nowMs = Date.now()) {
|
||||
@@ -48,7 +50,9 @@ export function shouldRefreshCredentials(provider, credentials, nowMs = Date.now
|
||||
return true;
|
||||
}
|
||||
|
||||
if (provider === "codex" && credentials.refreshToken && isCodexRefreshStale(credentials, nowMs)) {
|
||||
// Proactive stale refresh for providers declaring oauth.maxRefreshAgeMs (e.g. codex)
|
||||
const maxAgeMs = PROVIDER_OAUTH[provider]?.maxRefreshAgeMs;
|
||||
if (maxAgeMs && credentials.refreshToken && isCodexRefreshStale(credentials, nowMs, maxAgeMs)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -101,8 +105,9 @@ export function mergeRefreshedCredentials(provider, currentCredentials, refreshe
|
||||
next.copilotTokenExpiresAt = refreshedCredentials.copilotTokenExpiresAt;
|
||||
}
|
||||
|
||||
// trackRefreshAt providers (e.g. codex) always stamp lastRefreshAt for staleness tracking
|
||||
if (
|
||||
provider === "codex" ||
|
||||
PROVIDER_OAUTH[provider]?.trackRefreshAt ||
|
||||
next.accessToken ||
|
||||
next.apiKey ||
|
||||
next.token ||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { PROVIDERS } from "../config/providers.js";
|
||||
import { OPENAI_COMPAT_BASE, ANTHROPIC_COMPAT_BASE } from "../providers/shared.js";
|
||||
|
||||
const OPENAI_COMPATIBLE_PREFIX = "openai-compatible-";
|
||||
const OPENAI_COMPATIBLE_DEFAULTS = {
|
||||
baseUrl: "https://api.openai.com/v1",
|
||||
baseUrl: OPENAI_COMPAT_BASE,
|
||||
};
|
||||
|
||||
const ANTHROPIC_COMPATIBLE_PREFIX = "anthropic-compatible-";
|
||||
const ANTHROPIC_COMPATIBLE_DEFAULTS = {
|
||||
baseUrl: "https://api.anthropic.com/v1",
|
||||
baseUrl: ANTHROPIC_COMPAT_BASE,
|
||||
};
|
||||
|
||||
function isOpenAICompatible(provider) {
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
import { createHash } from "crypto";
|
||||
|
||||
import { proxyAwareFetch } from "../utils/proxyFetch.js";
|
||||
import { buildCosyHeaders } from "@/lib/qoder/cosy.js";
|
||||
import { buildCosyHeaders } from "../shared/qoder/cosy.js";
|
||||
import {
|
||||
QODER_MODEL_LIST_URL,
|
||||
} from "@/lib/qoder/constants.js";
|
||||
} from "../shared/qoder/constants.js";
|
||||
|
||||
const FETCH_TIMEOUT_MS = 15_000;
|
||||
const CACHE_TTL_MS = 60 * 60 * 1000; // 1h, same as the Kiro catalog
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PROVIDERS } from "../config/providers.js";
|
||||
import { PROVIDERS, PROVIDER_OAUTH } from "../config/providers.js";
|
||||
import { OAUTH_ENDPOINTS, GITHUB_COPILOT, REFRESH_LEAD_MS } from "../config/appConstants.js";
|
||||
import { proxyAwareFetch } from "../utils/proxyFetch.js";
|
||||
|
||||
@@ -574,7 +574,7 @@ export async function refreshCopilotToken(githubAccessToken, log) {
|
||||
if (!githubAccessToken) return null;
|
||||
return dedupRefresh("copilot", githubAccessToken, async () => {
|
||||
try {
|
||||
const response = await fetch("https://api.github.com/copilot_internal/v2/token", {
|
||||
const response = await fetch(PROVIDER_OAUTH["github"]?.copilotTokenUrl, {
|
||||
headers: {
|
||||
"Authorization": `token ${githubAccessToken}`,
|
||||
"User-Agent": GITHUB_COPILOT.USER_AGENT,
|
||||
@@ -790,12 +790,12 @@ export async function refreshVertexToken(saJson, log) {
|
||||
const jwt = await new SignJWT({ scope: "https://www.googleapis.com/auth/cloud-platform" })
|
||||
.setProtectedHeader({ alg: "RS256" })
|
||||
.setIssuer(saJson.client_email)
|
||||
.setAudience("https://oauth2.googleapis.com/token")
|
||||
.setAudience(OAUTH_ENDPOINTS.google.token)
|
||||
.setIssuedAt(now)
|
||||
.setExpirationTime(now + 3600)
|
||||
.sign(privateKey);
|
||||
|
||||
const res = await fetch("https://oauth2.googleapis.com/token", {
|
||||
const res = await fetch(OAUTH_ENDPOINTS.google.token, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: new URLSearchParams({
|
||||
|
||||
@@ -5,7 +5,11 @@
|
||||
import { CLIENT_METADATA, getPlatformUserAgent } from "../config/appConstants.js";
|
||||
import { proxyAwareFetch } from "../utils/proxyFetch.js";
|
||||
import { resolveDefaultProfileArn } from "../config/kiroConstants.js";
|
||||
import { ANTIGRAVITY_OAUTH_CLIENT } from "../providers/shared.js";
|
||||
import { ANTIGRAVITY_OAUTH_CLIENT, ANTHROPIC_API_VERSION } from "../providers/shared.js";
|
||||
import { PROVIDERS } from "../providers/index.js";
|
||||
|
||||
// usage endpoints: single source from registry transport.usage
|
||||
const U = (id) => PROVIDERS[id]?.usage || {};
|
||||
|
||||
// GitHub API config
|
||||
const GITHUB_CONFIG = {
|
||||
@@ -13,49 +17,40 @@ const GITHUB_CONFIG = {
|
||||
userAgent: "GitHubCopilotChat/0.26.7",
|
||||
};
|
||||
|
||||
// GLM quota endpoints (region-aware)
|
||||
// GLM quota endpoints (region-aware) — url from registry transport.usage
|
||||
const GLM_QUOTA_URLS = {
|
||||
international: "https://api.z.ai/api/monitor/usage/quota/limit",
|
||||
china: "https://open.bigmodel.cn/api/monitor/usage/quota/limit",
|
||||
international: U("glm").url,
|
||||
china: U("glm-cn").url,
|
||||
};
|
||||
|
||||
// MiniMax usage endpoints (try in order, fallback on transient errors)
|
||||
const MINIMAX_USAGE_URLS = {
|
||||
minimax: [
|
||||
"https://www.minimax.io/v1/token_plan/remains",
|
||||
"https://api.minimax.io/v1/api/openplatform/coding_plan/remains",
|
||||
],
|
||||
"minimax-cn": [
|
||||
"https://www.minimaxi.com/v1/api/openplatform/coding_plan/remains",
|
||||
"https://api.minimaxi.com/v1/api/openplatform/coding_plan/remains",
|
||||
],
|
||||
minimax: U("minimax").urls,
|
||||
"minimax-cn": U("minimax-cn").urls,
|
||||
};
|
||||
|
||||
// Vercel AI Gateway credits endpoint
|
||||
// Returns { balance: "95.50", total_used: "4.50" } (USD as decimal strings).
|
||||
// Docs: https://vercel.com/docs/ai-gateway/usage
|
||||
const VERCEL_AI_GATEWAY_CREDITS_URL = "https://ai-gateway.vercel.sh/v1/credits";
|
||||
const VERCEL_AI_GATEWAY_CREDITS_URL = U("vercel-ai-gateway").url;
|
||||
|
||||
// Antigravity API config (from Quotio)
|
||||
// Antigravity API config (from Quotio) — urls from registry, oauth client + dynamic UA kept here
|
||||
const ANTIGRAVITY_CONFIG = {
|
||||
quotaApiUrl: "https://cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels",
|
||||
loadProjectApiUrl: "https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist",
|
||||
tokenUrl: "https://oauth2.googleapis.com/token",
|
||||
...U("antigravity"),
|
||||
...ANTIGRAVITY_OAUTH_CLIENT,
|
||||
userAgent: getPlatformUserAgent(),
|
||||
};
|
||||
|
||||
// Codex (OpenAI) API config
|
||||
const CODEX_CONFIG = {
|
||||
usageUrl: "https://chatgpt.com/backend-api/wham/usage",
|
||||
usageUrl: U("codex").url,
|
||||
};
|
||||
|
||||
// Claude API config
|
||||
// Claude API config (urls from registry, apiVersion is header logic kept here)
|
||||
const CLAUDE_CONFIG = {
|
||||
oauthUsageUrl: "https://api.anthropic.com/api/oauth/usage",
|
||||
usageUrl: "https://api.anthropic.com/v1/organizations/{org_id}/usage",
|
||||
settingsUrl: "https://api.anthropic.com/v1/settings",
|
||||
apiVersion: "2023-06-01",
|
||||
oauthUsageUrl: U("claude").oauthUrl,
|
||||
usageUrl: U("claude").orgUrl,
|
||||
settingsUrl: U("claude").settingsUrl,
|
||||
apiVersion: ANTHROPIC_API_VERSION,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -139,7 +134,7 @@ async function getGitHubUsage(accessToken, providerSpecificData, proxyOptions =
|
||||
}
|
||||
|
||||
// copilot_internal/user API requires GitHub OAuth token, not copilotToken
|
||||
const response = await proxyAwareFetch("https://api.github.com/copilot_internal/user", {
|
||||
const response = await proxyAwareFetch(U("github").url, {
|
||||
headers: {
|
||||
"Authorization": `token ${accessToken}`,
|
||||
"Accept": "application/json",
|
||||
@@ -249,7 +244,7 @@ async function getGeminiUsage(accessToken, providerSpecificData, proxyOptions =
|
||||
let response;
|
||||
try {
|
||||
response = await proxyAwareFetch(
|
||||
"https://cloudcode-pa.googleapis.com/v1internal:retrieveUserQuota",
|
||||
U("gemini-cli").quotaUrl,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
@@ -313,7 +308,7 @@ async function getGeminiSubscriptionInfo(accessToken, proxyOptions = null) {
|
||||
const timeoutId = setTimeout(() => controller.abort(), 10000);
|
||||
try {
|
||||
const response = await proxyAwareFetch(
|
||||
"https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist",
|
||||
U("gemini-cli").loadCodeAssistUrl,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
@@ -765,7 +760,7 @@ async function getKiroUsage(accessToken, providerSpecificData, proxyOptions = nu
|
||||
{
|
||||
name: "codewhisperer-get",
|
||||
run: async () => proxyAwareFetch(
|
||||
`https://codewhisperer.us-east-1.amazonaws.com/getUsageLimits?${getUsageParams.toString()}`,
|
||||
`${U("kiro").cwHost}${U("kiro").limitsPath}?${getUsageParams.toString()}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
@@ -780,7 +775,7 @@ async function getKiroUsage(accessToken, providerSpecificData, proxyOptions = nu
|
||||
},
|
||||
{
|
||||
name: "codewhisperer-post",
|
||||
run: async () => proxyAwareFetch("https://codewhisperer.us-east-1.amazonaws.com", {
|
||||
run: async () => proxyAwareFetch(U("kiro").cwHost, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${accessToken}`,
|
||||
@@ -803,7 +798,7 @@ async function getKiroUsage(accessToken, providerSpecificData, proxyOptions = nu
|
||||
profileArn,
|
||||
resourceType: "AGENTIC_REQUEST",
|
||||
});
|
||||
return proxyAwareFetch(`https://q.us-east-1.amazonaws.com/getUsageLimits?${params}`, {
|
||||
return proxyAwareFetch(`${U("kiro").qHost}${U("kiro").limitsPath}?${params}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${accessToken}`,
|
||||
@@ -1289,7 +1284,7 @@ async function getQoderUsage(accessToken, proxyOptions = null) {
|
||||
}
|
||||
try {
|
||||
const response = await proxyAwareFetch(
|
||||
"https://openapi.qoder.sh/api/v2/quota/usage",
|
||||
U("qoder").url,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
|
||||
Reference in New Issue
Block a user