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,11 +1,80 @@
|
||||
import { BaseExecutor } from "./base.js";
|
||||
import { PROVIDERS } from "../config/providers.js";
|
||||
import { PROVIDERS, PROVIDER_OAUTH } from "../config/providers.js";
|
||||
import { ANTHROPIC_API_VERSION, OPENAI_COMPAT_BASE, ANTHROPIC_COMPAT_BASE } from "../providers/shared.js";
|
||||
import { OAUTH_ENDPOINTS, buildKimiHeaders } from "../config/appConstants.js";
|
||||
import { buildClineHeaders } from "../shared/clineAuth.js";
|
||||
import { getCachedClaudeHeaders } from "../utils/claudeHeaderCache.js";
|
||||
import { proxyAwareFetch } from "../utils/proxyFetch.js";
|
||||
import { injectReasoningContent } from "../utils/reasoningContentInjector.js";
|
||||
|
||||
// Auth header descriptors — derived from registry transport.auth, fallback to hardcoded defaults.
|
||||
const BEARER = { combined: true, header: "Authorization", scheme: "bearer" };
|
||||
const XAPIKEY = { combined: true, header: "x-api-key", scheme: "raw" };
|
||||
const AUTH_DESCRIPTORS = Object.fromEntries(
|
||||
Object.entries(PROVIDERS)
|
||||
.filter(([, t]) => t.auth)
|
||||
.map(([id, t]) => [id, t.auth])
|
||||
);
|
||||
|
||||
// Apply a token to a header per scheme (matches legacy: combined always sets, even when undefined).
|
||||
function setAuth(headers, spec, token) {
|
||||
headers[spec.header] = spec.scheme === "bearer" ? `Bearer ${token}` : token;
|
||||
}
|
||||
|
||||
// Resolve auth onto headers from a descriptor.
|
||||
function applyAuth(headers, desc, credentials) {
|
||||
if (desc.combined) {
|
||||
// combined providers always set the header (legacy behavior, incl. noAuth → "Bearer undefined")
|
||||
setAuth(headers, desc, credentials.apiKey || credentials.accessToken);
|
||||
if (desc.anthropicVersion && !headers["anthropic-version"]) headers["anthropic-version"] = ANTHROPIC_API_VERSION;
|
||||
return;
|
||||
}
|
||||
// split apiKey/oauth: set only the matching branch (legacy: anthropic-compatible skips when both absent)
|
||||
if (credentials.apiKey) setAuth(headers, desc.apiKey, credentials.apiKey);
|
||||
else if (credentials.accessToken) setAuth(headers, desc.oauth, credentials.accessToken);
|
||||
if (desc.anthropicVersion && !headers["anthropic-version"]) headers["anthropic-version"] = ANTHROPIC_API_VERSION;
|
||||
}
|
||||
|
||||
// Provider-specific header quirks kept as small hooks (not pure auth).
|
||||
const HEADER_HOOKS = {
|
||||
kimiHeaders: (h) => Object.assign(h, buildKimiHeaders()),
|
||||
clineHeaders: (h, c) => Object.assign(h, buildClineHeaders(c.apiKey || c.accessToken)),
|
||||
kilocodeOrg: (h, c) => { if (c.providerSpecificData?.orgId) h["X-Kilocode-OrganizationID"] = c.providerSpecificData.orgId; },
|
||||
claudeOverlay: (h) => {
|
||||
const cached = getCachedClaudeHeaders();
|
||||
if (!cached) return;
|
||||
for (const lcKey of Object.keys(cached)) {
|
||||
const titleKey = lcKey.replace(/(^|-)([a-z])/g, (_, sep, ch) => sep + ch.toUpperCase());
|
||||
if (lcKey === "anthropic-beta") {
|
||||
const staticBetaStr = h[titleKey] || h[lcKey] || "";
|
||||
const flags = new Set(staticBetaStr.split(",").map(f => f.trim()).filter(Boolean));
|
||||
for (const f of cached[lcKey].split(",").map(f => f.trim()).filter(Boolean)) flags.add(f);
|
||||
cached[lcKey] = Array.from(flags).join(",");
|
||||
}
|
||||
if (titleKey !== lcKey && h[titleKey] !== undefined) delete h[titleKey];
|
||||
}
|
||||
Object.assign(h, cached);
|
||||
},
|
||||
};
|
||||
|
||||
// Config-driven OAuth refresh grants — derived from registry oauth.refresh.
|
||||
const REFRESH_GRANTS = Object.fromEntries(
|
||||
Object.entries(PROVIDER_OAUTH)
|
||||
.filter(([, o]) => o.refresh)
|
||||
.map(([id, o]) => {
|
||||
const tokenUrl = o.tokenUrl;
|
||||
const encoding = o.refresh.encoding;
|
||||
const extraParams = o.refresh.scope ? { scope: o.refresh.scope } : {};
|
||||
return [id, {
|
||||
encoding,
|
||||
url: () => tokenUrl,
|
||||
params: (ex) => id === "gemini"
|
||||
? { client_id: ex.config.clientId, client_secret: ex.config.clientSecret, ...extraParams }
|
||||
: { client_id: o.clientId, ...extraParams },
|
||||
}];
|
||||
})
|
||||
);
|
||||
|
||||
export class DefaultExecutor extends BaseExecutor {
|
||||
constructor(provider) {
|
||||
super(provider, PROVIDERS[provider] || PROVIDERS.openai);
|
||||
@@ -15,7 +84,8 @@ export class DefaultExecutor extends BaseExecutor {
|
||||
const transformed = this.applyJsonSchemaFallback(body);
|
||||
|
||||
if (transformed && typeof transformed === "object") {
|
||||
if (this.provider === "cerebras" || this.provider === "mistral") {
|
||||
// quirk: some openai-compatible providers reject Anthropic's client_metadata field
|
||||
if (this.config.quirks?.dropClientMetadata) {
|
||||
delete transformed.client_metadata;
|
||||
}
|
||||
}
|
||||
@@ -45,13 +115,13 @@ export class DefaultExecutor extends BaseExecutor {
|
||||
|
||||
buildUrl(model, stream, urlIndex = 0, credentials = null) {
|
||||
if (this.provider?.startsWith?.("openai-compatible-")) {
|
||||
const baseUrl = credentials?.providerSpecificData?.baseUrl || "https://api.openai.com/v1";
|
||||
const baseUrl = credentials?.providerSpecificData?.baseUrl || OPENAI_COMPAT_BASE;
|
||||
const normalized = baseUrl.replace(/\/$/, "");
|
||||
const path = this.provider.includes("responses") ? "/responses" : "/chat/completions";
|
||||
return `${normalized}${path}`;
|
||||
}
|
||||
if (this.provider?.startsWith?.("anthropic-compatible-")) {
|
||||
const baseUrl = credentials?.providerSpecificData?.baseUrl || "https://api.anthropic.com/v1";
|
||||
const baseUrl = credentials?.providerSpecificData?.baseUrl || ANTHROPIC_COMPAT_BASE;
|
||||
const normalized = baseUrl.replace(/\/$/, "");
|
||||
return `${normalized}/messages`;
|
||||
}
|
||||
@@ -72,86 +142,23 @@ export class DefaultExecutor extends BaseExecutor {
|
||||
return url;
|
||||
}
|
||||
|
||||
// Fallback descriptor for providers without an explicit entry in AUTH_DESCRIPTORS.
|
||||
resolveAuthDescriptor() {
|
||||
if (this.provider?.startsWith?.("anthropic-compatible-")) {
|
||||
return { apiKey: { header: "x-api-key", scheme: "raw" }, oauth: { header: "Authorization", scheme: "bearer" }, anthropicVersion: true };
|
||||
}
|
||||
if (this.config?.format === "claude") {
|
||||
return { ...XAPIKEY, anthropicVersion: true };
|
||||
}
|
||||
return BEARER;
|
||||
}
|
||||
|
||||
buildHeaders(credentials, stream = true) {
|
||||
const headers = { "Content-Type": "application/json", ...this.config.headers };
|
||||
|
||||
switch (this.provider) {
|
||||
case "gemini":
|
||||
credentials.apiKey ? headers["x-goog-api-key"] = credentials.apiKey : headers["Authorization"] = `Bearer ${credentials.accessToken}`;
|
||||
break;
|
||||
case "claude": {
|
||||
// Overlay live cached headers from real Claude Code client over static defaults.
|
||||
// Static headers (Title-Case) remain as cold-start fallback.
|
||||
const cached = getCachedClaudeHeaders();
|
||||
if (cached) {
|
||||
// Remove Title-Case static keys that conflict with incoming lowercase cached keys
|
||||
for (const lcKey of Object.keys(cached)) {
|
||||
// Build the Title-Case equivalent: "anthropic-version" → "Anthropic-Version"
|
||||
const titleKey = lcKey.replace(/(^|-)([a-z])/g, (_, sep, c) => sep + c.toUpperCase());
|
||||
|
||||
// Special handling for Anthropic-Beta to preserve required flags like OAuth
|
||||
if (lcKey === "anthropic-beta") {
|
||||
const staticBetaStr = headers[titleKey] || headers[lcKey] || "";
|
||||
const staticFlags = new Set(staticBetaStr.split(",").map(f => f.trim()).filter(Boolean));
|
||||
const cachedFlags = new Set(cached[lcKey].split(",").map(f => f.trim()).filter(Boolean));
|
||||
|
||||
// Merge all static flags (which contain oauth, thinking, etc) into the cached ones
|
||||
for (const flag of staticFlags) {
|
||||
cachedFlags.add(flag);
|
||||
}
|
||||
|
||||
cached[lcKey] = Array.from(cachedFlags).join(",");
|
||||
}
|
||||
|
||||
if (titleKey !== lcKey && headers[titleKey] !== undefined) {
|
||||
delete headers[titleKey];
|
||||
}
|
||||
}
|
||||
Object.assign(headers, cached);
|
||||
}
|
||||
credentials.apiKey
|
||||
? (headers["x-api-key"] = credentials.apiKey)
|
||||
: (headers["Authorization"] = `Bearer ${credentials.accessToken}`);
|
||||
break;
|
||||
}
|
||||
case "glm":
|
||||
case "kimi":
|
||||
case "minimax":
|
||||
case "minimax-cn":
|
||||
case "kimi-coding":
|
||||
headers["x-api-key"] = credentials.apiKey || credentials.accessToken;
|
||||
if (this.provider === "kimi-coding") Object.assign(headers, buildKimiHeaders());
|
||||
break;
|
||||
default:
|
||||
if (this.provider?.startsWith?.("anthropic-compatible-")) {
|
||||
if (credentials.apiKey) {
|
||||
headers["x-api-key"] = credentials.apiKey;
|
||||
} else if (credentials.accessToken) {
|
||||
headers["Authorization"] = `Bearer ${credentials.accessToken}`;
|
||||
}
|
||||
if (!headers["anthropic-version"]) {
|
||||
headers["anthropic-version"] = "2023-06-01";
|
||||
}
|
||||
} else if (this.provider === "gitlab") {
|
||||
// GitLab Duo uses Bearer token (PAT with ai_features scope, or OAuth access token)
|
||||
headers["Authorization"] = `Bearer ${credentials.apiKey || credentials.accessToken}`;
|
||||
} else if (this.provider === "codebuddy") {
|
||||
headers["Authorization"] = `Bearer ${credentials.apiKey || credentials.accessToken}`;
|
||||
} else if (this.provider === "kilocode") {
|
||||
headers["Authorization"] = `Bearer ${credentials.apiKey || credentials.accessToken}`;
|
||||
if (credentials.providerSpecificData?.orgId) {
|
||||
headers["X-Kilocode-OrganizationID"] = credentials.providerSpecificData.orgId;
|
||||
}
|
||||
} else if (this.provider === "cline") {
|
||||
Object.assign(headers, buildClineHeaders(credentials.apiKey || credentials.accessToken));
|
||||
} else if (this.config?.format === "claude") {
|
||||
// Generic claude-format provider (e.g. agentrouter): x-api-key + anthropic-version
|
||||
headers["x-api-key"] = credentials.apiKey || credentials.accessToken;
|
||||
if (!headers["anthropic-version"]) headers["anthropic-version"] = "2023-06-01";
|
||||
} else {
|
||||
headers["Authorization"] = `Bearer ${credentials.apiKey || credentials.accessToken}`;
|
||||
}
|
||||
}
|
||||
const desc = AUTH_DESCRIPTORS[this.provider] || this.resolveAuthDescriptor();
|
||||
// Hooks run BEFORE auth so dynamic overlays (claude cached headers) can't clobber the token.
|
||||
for (const hook of desc.hooks || []) HEADER_HOOKS[hook]?.(headers, credentials);
|
||||
applyAuth(headers, desc, credentials);
|
||||
|
||||
// Strip first-party Claude Code identity headers for non-Anthropic anthropic-compatible upstreams
|
||||
if (this.provider?.startsWith?.("anthropic-compatible-")) {
|
||||
@@ -190,15 +197,25 @@ export class DefaultExecutor extends BaseExecutor {
|
||||
return headers;
|
||||
}
|
||||
|
||||
// Generic OAuth refresh for the common {grant_type, refresh_token, client_id[, ...]} shape.
|
||||
// grant = REFRESH_GRANTS[provider]; client creds resolved from PROVIDERS or this.config.
|
||||
refreshFromGrant(credentials, proxyOptions) {
|
||||
const grant = REFRESH_GRANTS[this.provider];
|
||||
const params = { grant_type: "refresh_token", refresh_token: credentials.refreshToken, ...grant.params(this) };
|
||||
return grant.encoding === "json"
|
||||
? this.refreshWithJSON(grant.url(), params, proxyOptions)
|
||||
: this.refreshWithForm(grant.url(), params, proxyOptions);
|
||||
}
|
||||
|
||||
async refreshCredentials(credentials, log, proxyOptions = null) {
|
||||
if (!credentials.refreshToken) return null;
|
||||
|
||||
const refreshers = {
|
||||
claude: () => this.refreshWithJSON(OAUTH_ENDPOINTS.anthropic.token, { grant_type: "refresh_token", refresh_token: credentials.refreshToken, client_id: PROVIDERS.claude.clientId }, proxyOptions),
|
||||
codex: () => this.refreshWithForm(OAUTH_ENDPOINTS.openai.token, { grant_type: "refresh_token", refresh_token: credentials.refreshToken, client_id: PROVIDERS.codex.clientId, scope: "openid profile email offline_access" }, proxyOptions),
|
||||
claude: () => this.refreshFromGrant(credentials, proxyOptions),
|
||||
codex: () => this.refreshFromGrant(credentials, proxyOptions),
|
||||
qwen: () => this.refreshWithForm(OAUTH_ENDPOINTS.qwen.token, { grant_type: "refresh_token", refresh_token: credentials.refreshToken, client_id: PROVIDERS.qwen.clientId }, proxyOptions),
|
||||
iflow: () => this.refreshIflow(credentials.refreshToken, proxyOptions),
|
||||
gemini: () => this.refreshGoogle(credentials.refreshToken, proxyOptions),
|
||||
gemini: () => this.refreshFromGrant(credentials, proxyOptions),
|
||||
kiro: () => this.refreshKiro(credentials.refreshToken, proxyOptions),
|
||||
cline: () => this.refreshCline(credentials.refreshToken, proxyOptions),
|
||||
"kimi-coding": () => this.refreshKimiCoding(credentials.refreshToken, proxyOptions),
|
||||
@@ -252,17 +269,6 @@ export class DefaultExecutor extends BaseExecutor {
|
||||
return { accessToken: tokens.access_token, refreshToken: tokens.refresh_token || refreshToken, expiresIn: tokens.expires_in };
|
||||
}
|
||||
|
||||
async refreshGoogle(refreshToken, proxyOptions = null) {
|
||||
const response = await proxyAwareFetch(OAUTH_ENDPOINTS.google.token, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json" },
|
||||
body: new URLSearchParams({ grant_type: "refresh_token", refresh_token: refreshToken, client_id: this.config.clientId, client_secret: this.config.clientSecret })
|
||||
}, proxyOptions);
|
||||
if (!response.ok) return null;
|
||||
const tokens = await response.json();
|
||||
return { accessToken: tokens.access_token, refreshToken: tokens.refresh_token || refreshToken, expiresIn: tokens.expires_in };
|
||||
}
|
||||
|
||||
async refreshKiro(refreshToken, proxyOptions = null) {
|
||||
const response = await proxyAwareFetch(PROVIDERS.kiro.tokenUrl, {
|
||||
method: "POST",
|
||||
|
||||
Reference in New Issue
Block a user