mirror of
https://github.com/cita-777/metapi.git
synced 2026-11-03 22:42:09 +08:00
feat: add OrcaRouter as a named platform and provider preset
* feat: add OrcaRouter as a named platform and provider preset Add a dedicated OrcaRouter adapter (OpenAI-compatible, API-key-first), auto-detected for api.orcarouter.ai, and registered across the platform registry, URL detection, site initialization presets, brand matching, and the web UI (site options, payload rules, connection segments). Verified: 2581 tests passing, full typecheck green, and a live model discovery call against https://api.orcarouter.ai returns 200 models. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: JinhaoSong322 <jinhao.song@myflashcloud.com> * fix: bound OrcaRouter host detection --------- Signed-off-by: JinhaoSong322 <jinhao.song@myflashcloud.com> Co-authored-by: JinhaoSong322 <jinhao.song@myflashcloud.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: cita-777 <juricek.chen@gmail.com>
This commit is contained in:
co-authored by
Claude
JinhaoSong322
cita-777
parent
8a7dfc5336
commit
8bd2bfec48
@@ -71,8 +71,8 @@
|
||||
**Metapi** 作为这些中转站之上的**元聚合层(Meta-Aggregation Layer)**,把多个站点统一到 **一个入口(可按项目配置多个下游 API Key)**——下游所有工具(Cursor、Claude Code、Codex、Open WebUI 等)即可无感接入全部模型。当前支持的上游范围已经不止传统聚合面板,还包括:
|
||||
|
||||
- 聚合面板: [New API](https://github.com/QuantumNous/new-api)、[One API](https://github.com/songquanpeng/one-api)、[OneHub](https://github.com/MartialBE/one-hub)、[DoneHub](https://github.com/deanxv/done-hub)、[Veloera](https://github.com/Veloera/Veloera)、[AnyRouter](https://anyrouter.top)、[Sub2API](https://github.com/Wei-Shaw/sub2api)
|
||||
- 通用兼容接口:OpenAI / Claude / Gemini compatible endpoints,以及 `cliproxyapi` / CPA
|
||||
- 官方预设:阿里云 / 智谱 / 豆包 Coding Plan,DeepSeek,Moonshot(Kimi),MiniMax,ModelScope
|
||||
- 通用兼容接口:OpenAI / Claude / Gemini compatible endpoints,以及 `cliproxyapi` / CPA、[OrcaRouter](https://www.orcarouter.ai)
|
||||
- 官方预设:阿里云 / 智谱 / 豆包 Coding Plan,DeepSeek,Moonshot(Kimi),MiniMax,ModelScope,OrcaRouter
|
||||
- OAuth 连接:Codex、Claude、Gemini CLI、Antigravity
|
||||
|
||||
详细接法见 [上游接入](./docs/upstream-integration.md) 与 [OAuth 管理](./docs/oauth.md)。
|
||||
@@ -196,8 +196,9 @@
|
||||
| **Veloera** | `veloera` | API 网关平台 |
|
||||
| **AnyRouter** | `anyrouter` | 通用路由平台 |
|
||||
| **Sub2API** | `sub2api` | 订阅制中转平台 |
|
||||
| **OrcaRouter** | `orcarouter` | 官方 OpenAI 兼容网关 |
|
||||
|
||||
各平台适配器覆盖模型枚举、余额查询、Token 管理、代理接入等通用能力;登录、签到、用户信息等能力按平台而异。
|
||||
各平台适配器覆盖模型枚举、代理接入等通用能力;余额查询和 Token 管理仅在上游提供相应接口的平台可用(OrcaRouter 目前不提供这两项能力);登录、签到、用户信息等能力按平台而异。
|
||||
|
||||
### 👥 账号与 Token 管理
|
||||
|
||||
|
||||
+3
-1
@@ -92,6 +92,7 @@ The AI ecosystem is seeing a growing number of aggregation relay stations based
|
||||
- [Veloera](https://github.com/Veloera/Veloera)
|
||||
- [AnyRouter](https://anyrouter.top) — Universal routing platform
|
||||
- [Sub2API](https://github.com/Wei-Shaw/sub2api) — Subscription-based relay
|
||||
- [OrcaRouter](https://www.orcarouter.ai) — OpenAI-compatible gateway with built-in agent security
|
||||
|
||||
| Pain Point | How Metapi Solves It |
|
||||
| --- | --- |
|
||||
@@ -219,8 +220,9 @@ The AI ecosystem is seeing a growing number of aggregation relay stations based
|
||||
| **Veloera** | `veloera` | API gateway platform |
|
||||
| **AnyRouter** | `anyrouter` | Universal routing platform |
|
||||
| **Sub2API** | `sub2api` | Subscription-based relay |
|
||||
| **OrcaRouter** | `orcarouter` | Official OpenAI-compatible gateway |
|
||||
|
||||
Adapters cover shared capabilities such as model discovery, balance access, token management, and proxy integration; login, check-in, and user-info flows vary by platform.
|
||||
Adapters cover shared capabilities such as model discovery and proxy integration; balance access and token management are available only where the upstream exposes those APIs (OrcaRouter currently does not); login, check-in, and user-info flows vary by platform.
|
||||
|
||||
### Account & Token Management
|
||||
|
||||
|
||||
@@ -139,8 +139,8 @@ npm run dev
|
||||
- 填写站点名称(自己想怎么取就怎么取)和 URL
|
||||
- 按你手上的上游形态选择:
|
||||
- 有后台面板:`new-api` / `one-api` / `one-hub` / `done-hub` / `veloera` / `anyrouter` / `sub2api`
|
||||
- 通用兼容接口:`openai` / `claude` / `gemini` / `cliproxyapi`
|
||||
- 官方入口:直接在下拉里选对应**官方预设**,例如阿里云 / 智谱 / 豆包 Coding Plan,DeepSeek,Moonshot,MiniMax,ModelScope
|
||||
- 通用兼容接口:`openai` / `claude` / `gemini` / `cliproxyapi` / `orcarouter`
|
||||
- 官方入口:直接在下拉里选对应**官方预设**,例如阿里云 / 智谱 / 豆包 Coding Plan,DeepSeek,Moonshot,MiniMax,ModelScope,OrcaRouter
|
||||
- 平台通常可自动检测;如果因为防护页、反向代理或特殊路径导致检测失败,再手动选择。
|
||||
- 可选是否开启系统代理,方便国内机器访问国外中转站。
|
||||
- 可选站点权重,站点权重越大,路由将更加频繁使用这个站点的模型。
|
||||
@@ -192,6 +192,7 @@ npm run dev
|
||||
适合这一分支的平台:
|
||||
|
||||
- `openai`
|
||||
- `orcarouter`
|
||||
- `claude`
|
||||
- `gemini`
|
||||
- `cliproxyapi`
|
||||
|
||||
@@ -276,6 +276,37 @@ CPA 这类站点推荐直接使用 **API Key**:
|
||||
|
||||
---
|
||||
|
||||
### OrcaRouter
|
||||
|
||||
**适用平台:** [OrcaRouter](https://www.orcarouter.ai) 官方 OpenAI 兼容网关
|
||||
|
||||
#### 站点配置
|
||||
|
||||
| 字段 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| **站点名称** | 自定义名称 | `OrcaRouter 网关` |
|
||||
| **站点 URL** | OrcaRouter API 端点(**不含** `/v1` 后缀) | `https://api.orcarouter.ai` |
|
||||
| **平台类型** | 选择 `orcarouter` | - |
|
||||
|
||||
#### 账号凭证
|
||||
|
||||
**仅支持 API Key:**
|
||||
|
||||
- 在「API Token」字段填入:`sk-orca-xxxxxxxxxxxxxx`
|
||||
- 不支持用户名密码登录(OrcaRouter 无此接口)
|
||||
|
||||
#### 功能限制
|
||||
|
||||
| 功能 | 支持情况 |
|
||||
|------|----------|
|
||||
| 模型列表获取 | ✅ 支持(`/v1/models`) |
|
||||
| 代理调用 | ✅ 支持 |
|
||||
| 余额查询 | ❌ 不支持(OrcaRouter 无公开接口) |
|
||||
| 自动签到 | ❌ 不适用 |
|
||||
| 账号令牌管理 | ❌ 不适用 |
|
||||
|
||||
---
|
||||
|
||||
### Claude (Anthropic)
|
||||
|
||||
**适用场景:** 直连 Anthropic Claude API
|
||||
|
||||
@@ -6,7 +6,7 @@ export const sites = sqliteTable('sites', {
|
||||
name: text('name').notNull(),
|
||||
url: text('url').notNull(),
|
||||
externalCheckinUrl: text('external_checkin_url'),
|
||||
platform: text('platform').notNull(), // 'new-api' | 'one-api' | 'veloera' | 'one-hub' | 'done-hub' | 'sub2api' | 'openai' | 'claude' | 'gemini' | 'codex' | 'gemini-cli' | 'antigravity'
|
||||
platform: text('platform').notNull(), // 'new-api' | 'one-api' | 'veloera' | 'one-hub' | 'done-hub' | 'sub2api' | 'orcarouter' | 'openai' | 'claude' | 'gemini' | 'codex' | 'gemini-cli' | 'antigravity'
|
||||
proxyUrl: text('proxy_url'),
|
||||
useSystemProxy: integer('use_system_proxy', { mode: 'boolean' }).default(false),
|
||||
customHeaders: text('custom_headers'),
|
||||
|
||||
@@ -6,6 +6,7 @@ describe('brandMatcher', () => {
|
||||
const brands = getAllBrandNames();
|
||||
|
||||
expect(brands).toContain('OpenRouter');
|
||||
expect(brands).toContain('OrcaRouter');
|
||||
expect(brands).toContain('Groq');
|
||||
expect(brands).toContain('DeepInfra');
|
||||
expect(brands).toContain('Fireworks');
|
||||
|
||||
@@ -68,6 +68,10 @@ describe('getAdapter platform aliases', () => {
|
||||
expect(getAdapter('chatgpt-codex')?.platformName).toBe('codex');
|
||||
});
|
||||
|
||||
it('supports the dedicated orcarouter adapter', () => {
|
||||
expect(getAdapter('orcarouter')?.platformName).toBe('orcarouter');
|
||||
});
|
||||
|
||||
it('detects anyrouter URL before generic new-api adapter', async () => {
|
||||
const adapter = await detectPlatform('https://anyrouter.top');
|
||||
expect(adapter?.platformName).toBe('anyrouter');
|
||||
@@ -78,6 +82,16 @@ describe('getAdapter platform aliases', () => {
|
||||
expect(adapter?.platformName).toBe('done-hub');
|
||||
});
|
||||
|
||||
it('detects orcarouter URL via the dedicated adapter', async () => {
|
||||
const adapter = await detectPlatform('https://api.orcarouter.ai');
|
||||
expect(adapter?.platformName).toBe('orcarouter');
|
||||
});
|
||||
|
||||
it('does not detect OrcaRouter from an unrelated URL component', async () => {
|
||||
const adapter = await detectPlatform('https://evil.example.com/orcarouter');
|
||||
expect(adapter?.platformName).not.toBe('orcarouter');
|
||||
});
|
||||
|
||||
it('detects official openai/claude/gemini upstream URLs', async () => {
|
||||
const openai = await detectPlatform('https://api.openai.com');
|
||||
const claude = await detectPlatform('https://api.anthropic.com');
|
||||
|
||||
@@ -7,6 +7,7 @@ import { OneHubAdapter } from './oneHub.js';
|
||||
import { DoneHubAdapter } from './doneHub.js';
|
||||
import { Sub2ApiAdapter } from './sub2api.js';
|
||||
import { OpenAiAdapter } from './openai.js';
|
||||
import { OrcaRouterAdapter } from './orcarouter.js';
|
||||
import { CodexAdapter } from './codex.js';
|
||||
import { ClaudeAdapter } from './claude.js';
|
||||
import { GeminiAdapter } from './gemini.js';
|
||||
@@ -25,6 +26,7 @@ const adapters: PlatformAdapter[] = [
|
||||
new GeminiCliAdapter(),
|
||||
new AntigravityAdapter(),
|
||||
new CliProxyApiAdapter(),
|
||||
new OrcaRouterAdapter(),
|
||||
new AnyRouterAdapter(),
|
||||
new DoneHubAdapter(),
|
||||
new OneHubAdapter(),
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { StandardApiProviderAdapterBase } from './standardApiProvider.js';
|
||||
import { detectPlatformByUrlHint } from '../../../shared/platformIdentity.js';
|
||||
|
||||
export class OrcaRouterAdapter extends StandardApiProviderAdapterBase {
|
||||
readonly platformName = 'orcarouter';
|
||||
|
||||
async detect(url: string): Promise<boolean> {
|
||||
return detectPlatformByUrlHint(url) === this.platformName;
|
||||
}
|
||||
|
||||
async getModels(baseUrl: string, apiToken: string): Promise<string[]> {
|
||||
return this.fetchModelsFromStandardEndpoint({
|
||||
baseUrl,
|
||||
headers: { Authorization: `Bearer ${apiToken}` },
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -10,5 +10,7 @@ describe('modelBrand matching helpers', () => {
|
||||
|
||||
expect(getBrand('openrouter/anthropic/claude-3-7-sonnet')?.name).toBe('Anthropic');
|
||||
expect(getBrand('deepinfra/meta-llama/llama-3.3-70b-instruct')?.name).toBe('Meta');
|
||||
|
||||
expect(getBrand('orcarouter/auto')?.name).toBe('OrcaRouter');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -511,6 +511,15 @@ const BRAND_DEFINITIONS: BrandDefinition[] = [
|
||||
{ keyword: 'openrouter-', mode: 'startsWith' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'OrcaRouter',
|
||||
icon: 'orcarouter',
|
||||
color: 'linear-gradient(135deg, #0ea5e9, #2563eb)',
|
||||
rules: [
|
||||
{ keyword: 'orcarouter', mode: 'includes' },
|
||||
{ keyword: 'orcarouter/', mode: 'startsWith' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Groq',
|
||||
icon: 'groq',
|
||||
|
||||
@@ -17,6 +17,7 @@ export const PLATFORM_ALIASES = Object.assign(Object.create(null), {
|
||||
'done-hub': 'done-hub',
|
||||
veloera: 'veloera',
|
||||
sub2api: 'sub2api',
|
||||
orcarouter: 'orcarouter',
|
||||
openai: 'openai',
|
||||
codex: 'codex',
|
||||
'chatgpt-codex': 'codex',
|
||||
@@ -89,6 +90,11 @@ export function detectPlatformByUrlHint(url) {
|
||||
if (host.includes('onehub') || host.includes('one-hub')) return 'one-hub';
|
||||
if (host.includes('veloera')) return 'veloera';
|
||||
if (host.includes('sub2api')) return 'sub2api';
|
||||
// OrcaRouter is an upstream API host, not a free-form URL marker. Keep the
|
||||
// match bounded to the official host (and its explicitly delegated
|
||||
// subdomains) so a token is never routed to an unrelated URL containing the
|
||||
// provider name in a path, query, or userinfo component.
|
||||
if (host === 'api.orcarouter.ai' || host.endsWith('.orcarouter.ai')) return 'orcarouter';
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ describe('platformIdentity', () => {
|
||||
expect(normalizePlatformAlias('chatgpt-codex')).toBe('codex');
|
||||
expect(normalizePlatformAlias('anti-gravity')).toBe('antigravity');
|
||||
expect(normalizePlatformAlias('one api')).toBe('one-api');
|
||||
expect(normalizePlatformAlias('orcarouter')).toBe('orcarouter');
|
||||
expect(normalizePlatformAlias('')).toBe('');
|
||||
});
|
||||
|
||||
@@ -20,6 +21,10 @@ describe('platformIdentity', () => {
|
||||
expect(detectPlatformByUrlHint('https://generativelanguage.googleapis.com/v1beta')).toBe('gemini');
|
||||
expect(detectPlatformByUrlHint('https://cloudcode-pa.googleapis.com')).toBe('gemini-cli');
|
||||
expect(detectPlatformByUrlHint('http://127.0.0.1:8317/v1/models')).toBe('cliproxyapi');
|
||||
expect(detectPlatformByUrlHint('https://api.orcarouter.ai/v1/models')).toBe('orcarouter');
|
||||
expect(detectPlatformByUrlHint('https://gateway.orcarouter.ai/v1/models')).toBe('orcarouter');
|
||||
expect(detectPlatformByUrlHint('https://evil.example.com/orcarouter/v1/models')).toBeUndefined();
|
||||
expect(detectPlatformByUrlHint('https://evil.example.com/?next=https://api.orcarouter.ai/v1/models')).toBeUndefined();
|
||||
expect(detectPlatformByUrlHint('https://evil.example.com/?next=https://api.openai.com/v1/models')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
+2
-1
@@ -11,7 +11,8 @@ export type SiteInitializationPresetId =
|
||||
| 'minimax-claude'
|
||||
| 'modelscope-openai'
|
||||
| 'modelscope-claude'
|
||||
| 'doubao-coding-openai';
|
||||
| 'doubao-coding-openai'
|
||||
| 'orcarouter-openai';
|
||||
export type SiteInitializationPreset = {
|
||||
id: SiteInitializationPresetId;
|
||||
label: string;
|
||||
|
||||
@@ -78,6 +78,10 @@ const DOUBAO_CODING_RECOMMENDED_MODELS = Object.freeze([
|
||||
'doubao-seed-2.0-pro',
|
||||
]);
|
||||
|
||||
const ORCAROUTER_RECOMMENDED_MODELS = Object.freeze([
|
||||
'orcarouter/auto',
|
||||
]);
|
||||
|
||||
const SITE_INITIALIZATION_PRESETS = Object.freeze([
|
||||
Object.freeze({
|
||||
id: 'codingplan-openai',
|
||||
@@ -274,6 +278,21 @@ const SITE_INITIALIZATION_PRESETS = Object.freeze([
|
||||
return matchesHostAndPaths(url, 'ark.cn-beijing.volces.com', ['/api/coding/v3']);
|
||||
},
|
||||
}),
|
||||
Object.freeze({
|
||||
id: 'orcarouter-openai',
|
||||
label: 'OrcaRouter / OpenAI',
|
||||
providerLabel: 'OrcaRouter',
|
||||
description: '适合 OrcaRouter 官方 OpenAI 兼容入口,用 sk-orca- API Key 直连网关并享受统一安全管控。',
|
||||
platform: 'orcarouter',
|
||||
defaultUrl: 'https://api.orcarouter.ai/v1',
|
||||
initialSegment: 'apikey',
|
||||
recommendedSkipModelFetch: true,
|
||||
recommendedModels: ORCAROUTER_RECOMMENDED_MODELS,
|
||||
docsUrl: 'https://www.orcarouter.ai',
|
||||
matches(url) {
|
||||
return matchesHostAndPaths(url, 'api.orcarouter.ai', ['/', '/v1']);
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
function clonePreset(preset) {
|
||||
|
||||
@@ -23,6 +23,7 @@ describe('siteInitializationPresets', () => {
|
||||
'modelscope-openai',
|
||||
'modelscope-claude',
|
||||
'doubao-coding-openai',
|
||||
'orcarouter-openai',
|
||||
]));
|
||||
|
||||
const openaiPreset = getSiteInitializationPreset('codingplan-openai');
|
||||
@@ -122,6 +123,16 @@ describe('siteInitializationPresets', () => {
|
||||
'doubao-seed-2.0-code',
|
||||
'doubao-seed-2.0-pro',
|
||||
]);
|
||||
|
||||
const orcaRouterPreset = getSiteInitializationPreset('orcarouter-openai');
|
||||
expect(orcaRouterPreset).toMatchObject({
|
||||
id: 'orcarouter-openai',
|
||||
platform: 'orcarouter',
|
||||
defaultUrl: 'https://api.orcarouter.ai/v1',
|
||||
initialSegment: 'apikey',
|
||||
recommendedSkipModelFetch: true,
|
||||
});
|
||||
expect(orcaRouterPreset?.recommendedModels).toEqual(['orcarouter/auto']);
|
||||
});
|
||||
|
||||
it('detects Aliyun CodingPlan endpoints by URL', () => {
|
||||
@@ -189,6 +200,15 @@ describe('siteInitializationPresets', () => {
|
||||
id: 'doubao-coding-openai',
|
||||
platform: 'openai',
|
||||
});
|
||||
|
||||
expect(detectSiteInitializationPreset('https://api.orcarouter.ai/v1')).toMatchObject({
|
||||
id: 'orcarouter-openai',
|
||||
platform: 'orcarouter',
|
||||
});
|
||||
expect(detectSiteInitializationPreset('https://api.orcarouter.ai')).toMatchObject({
|
||||
id: 'orcarouter-openai',
|
||||
platform: 'orcarouter',
|
||||
});
|
||||
});
|
||||
|
||||
it('reselects openai presets from canonicalized root urls when platform is already known', () => {
|
||||
|
||||
@@ -49,6 +49,7 @@ describe('getBrand', () => {
|
||||
|
||||
it('detects new provider and platform brands with lobe-hub icons', () => {
|
||||
expect(getBrand('openrouter/openrouter-auto')).toMatchObject({ name: 'OpenRouter', icon: expect.any(String) });
|
||||
expect(getBrand('orcarouter/auto')).toMatchObject({ name: 'OrcaRouter', icon: expect.any(String) });
|
||||
expect(getBrand('groq/compound-beta')).toMatchObject({ name: 'Groq', icon: expect.any(String) });
|
||||
expect(getBrand('deepinfra/deepinfra-chat')).toMatchObject({ name: 'DeepInfra', icon: expect.any(String) });
|
||||
expect(getBrand('fireworks-ai/firefunction-v2')).toMatchObject({ name: 'Fireworks', icon: expect.any(String) });
|
||||
|
||||
@@ -230,6 +230,7 @@ const platformColors: Record<string, string> = {
|
||||
'one-hub': 'badge-muted',
|
||||
'done-hub': 'badge-muted',
|
||||
sub2api: 'badge-muted',
|
||||
orcarouter: 'badge-warning',
|
||||
openai: 'badge-success',
|
||||
codex: 'badge-success',
|
||||
claude: 'badge-warning',
|
||||
@@ -246,6 +247,7 @@ const SITE_PLATFORM_OPTIONS = [
|
||||
{ value: 'one-hub', label: 'one-hub', description: '聚合面板,偏向多账号统一管理' },
|
||||
{ value: 'done-hub', label: 'done-hub', description: '聚合面板,适合统一转发与管理' },
|
||||
{ value: 'sub2api', label: 'sub2api', description: '订阅式中转面板,可同步套餐与余额信息' },
|
||||
{ value: 'orcarouter', label: 'orcarouter', description: 'OrcaRouter 官方 OpenAI 兼容网关,sk-orca- API Key 直连' },
|
||||
{ value: 'openai', label: 'openai', description: '通用 OpenAI 兼容接口,手填 Base URL 即可' },
|
||||
{ value: 'codex', label: 'codex', description: 'Codex OAuth / Session 优先入口' },
|
||||
{ value: 'claude', label: 'claude', description: '通用 Claude / Anthropic 兼容接口' },
|
||||
|
||||
@@ -15,6 +15,7 @@ describe('defaultConnectionSegment', () => {
|
||||
|
||||
it('maps API-key-only platforms to the apikey segment', () => {
|
||||
expect(resolveInitialConnectionSegment('openai')).toBe('apikey');
|
||||
expect(resolveInitialConnectionSegment('orcarouter')).toBe('apikey');
|
||||
expect(resolveInitialConnectionSegment('claude')).toBe('apikey');
|
||||
expect(resolveInitialConnectionSegment('gemini')).toBe('apikey');
|
||||
expect(resolveInitialConnectionSegment('cliproxyapi')).toBe('apikey');
|
||||
|
||||
@@ -13,6 +13,7 @@ const SESSION_FIRST_PLATFORMS = new Set([
|
||||
|
||||
const API_KEY_FIRST_PLATFORMS = new Set([
|
||||
'openai',
|
||||
'orcarouter',
|
||||
'claude',
|
||||
'gemini',
|
||||
'cliproxyapi',
|
||||
|
||||
@@ -9,6 +9,7 @@ describe('payload rule platform options', () => {
|
||||
'',
|
||||
'codex',
|
||||
'sub2api',
|
||||
'orcarouter',
|
||||
'new-api',
|
||||
'one-api',
|
||||
'cliproxyapi',
|
||||
|
||||
@@ -2,6 +2,7 @@ export const PAYLOAD_RULE_PROTOCOL_OPTIONS: Array<{ value: string; label: string
|
||||
{ value: '', label: '全部平台' },
|
||||
{ value: 'codex', label: 'Codex' },
|
||||
{ value: 'sub2api', label: 'Sub2API' },
|
||||
{ value: 'orcarouter', label: 'OrcaRouter' },
|
||||
{ value: 'new-api', label: 'New API' },
|
||||
{ value: 'one-api', label: 'One API' },
|
||||
{ value: 'cliproxyapi', label: 'CLIProxyAPI' },
|
||||
|
||||
@@ -29,6 +29,7 @@ export const PLATFORM_ENDPOINT_FALLBACK_MAP: Record<string, string[]> = {
|
||||
'one-hub': ['openai'],
|
||||
'done-hub': ['openai'],
|
||||
sub2api: ['openai'],
|
||||
orcarouter: ['openai'],
|
||||
veloera: ['openai'],
|
||||
cliproxyapi: ['openai'],
|
||||
claude: ['anthropic'],
|
||||
|
||||
Reference in New Issue
Block a user