refactor: share platform file and route helpers

This commit is contained in:
Cita
2026-03-22 22:17:50 +08:00
parent 9290e51ecc
commit 32d7037629
24 changed files with 375 additions and 333 deletions
+8 -63
View File
@@ -4,6 +4,7 @@ import { db, schema } from '../db/index.js';
import { upsertSetting } from '../db/upsertSetting.js';
import { mergeAccountExtraConfig } from './accountExtraConfig.js';
import { getOauthInfoFromAccount } from './oauth/oauthAccount.js';
import { PLATFORM_ALIASES, detectPlatformByUrlHint } from '../../shared/platformIdentity.js';
const BACKUP_VERSION = '2.1';
@@ -244,38 +245,6 @@ const DIRECT_API_PLATFORMS = new Set([
'antigravity',
]);
const IMPORT_PLATFORM_ALIASES: Record<string, string> = {
anyrouter: 'anyrouter',
'wong-gongyi': 'new-api',
'vo-api': 'new-api',
'super-api': 'new-api',
'rix-api': 'new-api',
'neo-api': 'new-api',
newapi: 'new-api',
'new api': 'new-api',
'new-api': 'new-api',
oneapi: 'one-api',
'one api': 'one-api',
'one-api': 'one-api',
onehub: 'one-hub',
'one-hub': 'one-hub',
donehub: 'done-hub',
'done-hub': 'done-hub',
veloera: 'veloera',
sub2api: 'sub2api',
openai: 'openai',
anthropic: 'claude',
claude: 'claude',
google: 'gemini',
gemini: 'gemini',
cliproxyapi: 'cliproxyapi',
cpa: 'cliproxyapi',
'cli-proxy-api': 'cliproxyapi',
codex: 'codex',
'gemini-cli': 'gemini-cli',
antigravity: 'antigravity',
};
function isRecord(value: unknown): value is Record<string, unknown> {
return !!value && typeof value === 'object' && !Array.isArray(value);
}
@@ -645,38 +614,14 @@ function normalizeOriginUrl(raw: string): string {
}
}
function detectLocalPlatformByUrlHint(url: string): string | undefined {
const normalized = url.trim().toLowerCase();
if (!normalized) return undefined;
if (normalized.includes('api.openai.com')) return 'openai';
if (normalized.includes('chatgpt.com/backend-api/codex')) return 'codex';
if (normalized.includes('api.anthropic.com') || normalized.includes('anthropic.com/v1')) return 'claude';
if (
normalized.includes('generativelanguage.googleapis.com')
|| normalized.includes('googleapis.com/v1beta/openai')
|| normalized.includes('gemini.google.com')
) {
return 'gemini';
}
if (normalized.includes('cloudcode-pa.googleapis.com')) return 'gemini-cli';
if (normalized.includes('anyrouter')) return 'anyrouter';
if (normalized.includes('donehub') || normalized.includes('done-hub')) return 'done-hub';
if (normalized.includes('onehub') || normalized.includes('one-hub')) return 'one-hub';
if (normalized.includes('veloera')) return 'veloera';
if (normalized.includes('sub2api')) return 'sub2api';
if (normalized.includes('127.0.0.1:8317') || normalized.includes('localhost:8317')) return 'cliproxyapi';
return undefined;
}
function resolveImportedPlatform(rawPlatform: unknown, rawUrl: string): string {
const normalizedPlatform = typeof rawPlatform === 'string'
? IMPORT_PLATFORM_ALIASES[rawPlatform.trim().toLowerCase()]
: undefined;
const rawPlatformText = asString(rawPlatform).toLowerCase();
const normalizedPlatform = rawPlatformText
? (PLATFORM_ALIASES[rawPlatformText] ?? (DIRECT_API_PLATFORMS.has(rawPlatformText) ? rawPlatformText : ''))
: '';
if (normalizedPlatform) return normalizedPlatform;
const urlHint = detectLocalPlatformByUrlHint(rawUrl);
const urlHint = detectPlatformByUrlHint(rawUrl);
if (urlHint) return urlHint;
return normalizeLegacyPlatform(asString(rawPlatform));
@@ -688,9 +633,9 @@ function resolveImportedProfilePlatform(apiType: unknown, baseUrl: string): stri
if (normalizedType === 'anthropic') return 'claude';
if (normalizedType === 'google') return 'gemini';
if (normalizedType === 'openai-compatible') {
return detectLocalPlatformByUrlHint(baseUrl) || 'openai';
return detectPlatformByUrlHint(baseUrl) || 'openai';
}
return detectLocalPlatformByUrlHint(baseUrl) || 'openai';
return detectPlatformByUrlHint(baseUrl) || 'openai';
}
function pushDefaultImportedToken(
+2 -3
View File
@@ -1,6 +1,5 @@
import { StandardApiProviderAdapterBase } from './standardApiProvider.js';
const DEFAULT_ANTHROPIC_VERSION = '2023-06-01';
import { CLAUDE_DEFAULT_ANTHROPIC_VERSION } from '../oauth/claudeProvider.js';
export class ClaudeAdapter extends StandardApiProviderAdapterBase {
readonly platformName = 'claude';
@@ -15,7 +14,7 @@ export class ClaudeAdapter extends StandardApiProviderAdapterBase {
baseUrl,
headers: {
'x-api-key': apiToken,
'anthropic-version': DEFAULT_ANTHROPIC_VERSION,
'anthropic-version': CLAUDE_DEFAULT_ANTHROPIC_VERSION,
},
});
}
+2 -70
View File
@@ -14,6 +14,7 @@ import { GeminiCliAdapter } from './geminiCli.js';
import { AntigravityAdapter } from './antigravity.js';
import { CliProxyApiAdapter } from './cliproxyapi.js';
import { detectPlatformByTitle } from './titleHint.js';
import { detectPlatformByUrlHint, normalizePlatformAlias } from '../../../shared/platformIdentity.js';
const adapters: PlatformAdapter[] = [
// Specific forks before generic adapters for better auto-detection.
@@ -33,47 +34,8 @@ const adapters: PlatformAdapter[] = [
new OneApiAdapter(),
];
const platformAliases: Record<string, string> = {
// NewAPI family aliases
anyrouter: 'anyrouter',
'wong-gongyi': 'new-api',
'vo-api': 'new-api',
'super-api': 'new-api',
'rix-api': 'new-api',
'neo-api': 'new-api',
newapi: 'new-api',
'new api': 'new-api',
// OneAPI family aliases
oneapi: 'one-api',
'one api': 'one-api',
// Keep canonical forms explicit for clarity
'new-api': 'new-api',
'one-api': 'one-api',
veloera: 'veloera',
'one-hub': 'one-hub',
'done-hub': 'done-hub',
sub2api: 'sub2api',
// Official upstream APIs
openai: 'openai',
codex: 'codex',
'chatgpt-codex': 'codex',
'chatgpt codex': 'codex',
anthropic: 'claude',
claude: 'claude',
gemini: 'gemini',
'gemini-cli': 'gemini-cli',
antigravity: 'antigravity',
'anti-gravity': 'antigravity',
google: 'gemini',
// CLIProxyAPI aliases
cliproxyapi: 'cliproxyapi',
cpa: 'cliproxyapi',
'cli-proxy-api': 'cliproxyapi',
};
function normalizePlatform(platform: string): string {
const raw = (platform || '').trim().toLowerCase();
return platformAliases[raw] ?? raw;
return normalizePlatformAlias(platform);
}
export function getAdapter(platform: string): PlatformAdapter | undefined {
@@ -89,36 +51,6 @@ const titleFirstPlatforms = new Set<string>([
'sub2api',
]);
function detectPlatformByUrlHint(url: string): string | undefined {
const normalized = (url || '').trim().toLowerCase();
if (!normalized) return undefined;
// Official upstream endpoints.
if (normalized.includes('api.openai.com')) return 'openai';
if (normalized.includes('chatgpt.com/backend-api/codex')) return 'codex';
if (normalized.includes('api.anthropic.com') || normalized.includes('anthropic.com/v1')) return 'claude';
if (
normalized.includes('generativelanguage.googleapis.com')
|| normalized.includes('googleapis.com/v1beta/openai')
|| normalized.includes('gemini.google.com')
) {
return 'gemini';
}
if (normalized.includes('cloudcode-pa.googleapis.com')) return 'gemini-cli';
// NewAPI-family forks and common aliases.
if (normalized.includes('anyrouter')) return 'anyrouter';
if (normalized.includes('donehub') || normalized.includes('done-hub')) return 'done-hub';
if (normalized.includes('onehub') || normalized.includes('one-hub')) return 'one-hub';
if (normalized.includes('veloera')) return 'veloera';
if (normalized.includes('sub2api')) return 'sub2api';
// CLIProxyAPI default local endpoints.
if (normalized.includes('127.0.0.1:8317') || normalized.includes('localhost:8317')) return 'cliproxyapi';
return undefined;
}
export async function detectPlatform(url: string): Promise<PlatformAdapter | undefined> {
const urlHint = detectPlatformByUrlHint(url);
if (urlHint) {
@@ -2,6 +2,7 @@ import type { ProxyResourceOwner } from '../middleware/auth.js';
import { getProxyFileByPublicIdForOwner } from './proxyFileStore.js';
import { ensureBase64DataUrl } from '../transformers/shared/inputFile.js';
import { summarizeConversationFileInputsInOpenAiBody } from '../proxy-core/capabilities/conversationFileCapabilities.js';
import { isSupportedConversationFileMimeType } from '../../shared/conversationFileTypes.js';
const LOCAL_PROXY_FILE_ID_PREFIX = 'file-metapi-';
@@ -44,12 +45,7 @@ function inferMimeTypeFromFilename(filename: string): string {
}
function isSupportedMimeType(mimeType: string): boolean {
return mimeType === 'application/pdf'
|| mimeType === 'text/plain'
|| mimeType === 'text/markdown'
|| mimeType === 'application/json'
|| mimeType.startsWith('image/')
|| mimeType.startsWith('audio/');
return isSupportedConversationFileMimeType(mimeType);
}
function audioFormatFromMimeType(mimeType: string, filename: string): string {