diff --git a/README.md b/README.md index f8a174a..f6722b0 100644 --- a/README.md +++ b/README.md @@ -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 管理 diff --git a/README_EN.md b/README_EN.md index b6abe37..93ce4d7 100644 --- a/README_EN.md +++ b/README_EN.md @@ -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 diff --git a/docs/getting-started.md b/docs/getting-started.md index 4818e1a..1a0dc19 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -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` diff --git a/docs/upstream-integration.md b/docs/upstream-integration.md index 67cf7e8..ff543d2 100644 --- a/docs/upstream-integration.md +++ b/docs/upstream-integration.md @@ -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 diff --git a/src/server/db/schema.ts b/src/server/db/schema.ts index 93288d7..d058eaf 100644 --- a/src/server/db/schema.ts +++ b/src/server/db/schema.ts @@ -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'), diff --git a/src/server/services/brandMatcher.test.ts b/src/server/services/brandMatcher.test.ts index 7ed86d6..bf91035 100644 --- a/src/server/services/brandMatcher.test.ts +++ b/src/server/services/brandMatcher.test.ts @@ -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'); diff --git a/src/server/services/platforms/index.test.ts b/src/server/services/platforms/index.test.ts index e726cd4..d7216a0 100644 --- a/src/server/services/platforms/index.test.ts +++ b/src/server/services/platforms/index.test.ts @@ -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'); diff --git a/src/server/services/platforms/index.ts b/src/server/services/platforms/index.ts index aceb911..0cc6e49 100644 --- a/src/server/services/platforms/index.ts +++ b/src/server/services/platforms/index.ts @@ -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(), diff --git a/src/server/services/platforms/orcarouter.ts b/src/server/services/platforms/orcarouter.ts new file mode 100644 index 0000000..779efc8 --- /dev/null +++ b/src/server/services/platforms/orcarouter.ts @@ -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 { + return detectPlatformByUrlHint(url) === this.platformName; + } + + async getModels(baseUrl: string, apiToken: string): Promise { + return this.fetchModelsFromStandardEndpoint({ + baseUrl, + headers: { Authorization: `Bearer ${apiToken}` }, + }); + } +} diff --git a/src/server/shared/modelBrand.test.ts b/src/server/shared/modelBrand.test.ts index 8697a4f..172c833 100644 --- a/src/server/shared/modelBrand.test.ts +++ b/src/server/shared/modelBrand.test.ts @@ -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'); }); }); diff --git a/src/server/shared/modelBrand.ts b/src/server/shared/modelBrand.ts index c27d0f3..0e61c90 100644 --- a/src/server/shared/modelBrand.ts +++ b/src/server/shared/modelBrand.ts @@ -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', diff --git a/src/shared/platformIdentity.js b/src/shared/platformIdentity.js index dd5adb9..d938ee1 100644 --- a/src/shared/platformIdentity.js +++ b/src/shared/platformIdentity.js @@ -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; } diff --git a/src/shared/platformIdentity.test.ts b/src/shared/platformIdentity.test.ts index 1244170..2457348 100644 --- a/src/shared/platformIdentity.test.ts +++ b/src/shared/platformIdentity.test.ts @@ -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(); }); }); diff --git a/src/shared/siteInitializationPresets.d.ts b/src/shared/siteInitializationPresets.d.ts index a9f4d8e..e46fac8 100644 --- a/src/shared/siteInitializationPresets.d.ts +++ b/src/shared/siteInitializationPresets.d.ts @@ -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; diff --git a/src/shared/siteInitializationPresets.js b/src/shared/siteInitializationPresets.js index 58f94ad..1763ff4 100644 --- a/src/shared/siteInitializationPresets.js +++ b/src/shared/siteInitializationPresets.js @@ -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) { diff --git a/src/shared/siteInitializationPresets.test.ts b/src/shared/siteInitializationPresets.test.ts index da2e899..3bd8c82 100644 --- a/src/shared/siteInitializationPresets.test.ts +++ b/src/shared/siteInitializationPresets.test.ts @@ -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', () => { diff --git a/src/web/components/BrandIcon.test.ts b/src/web/components/BrandIcon.test.ts index 66ba08f..79ff9f9 100644 --- a/src/web/components/BrandIcon.test.ts +++ b/src/web/components/BrandIcon.test.ts @@ -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) }); diff --git a/src/web/pages/Sites.tsx b/src/web/pages/Sites.tsx index a4d93a3..945458d 100644 --- a/src/web/pages/Sites.tsx +++ b/src/web/pages/Sites.tsx @@ -230,6 +230,7 @@ const platformColors: Record = { '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 兼容接口' }, diff --git a/src/web/pages/helpers/defaultConnectionSegment.test.ts b/src/web/pages/helpers/defaultConnectionSegment.test.ts index 99b109a..369a3d0 100644 --- a/src/web/pages/helpers/defaultConnectionSegment.test.ts +++ b/src/web/pages/helpers/defaultConnectionSegment.test.ts @@ -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'); diff --git a/src/web/pages/helpers/defaultConnectionSegment.ts b/src/web/pages/helpers/defaultConnectionSegment.ts index 21b3c9a..95968f0 100644 --- a/src/web/pages/helpers/defaultConnectionSegment.ts +++ b/src/web/pages/helpers/defaultConnectionSegment.ts @@ -13,6 +13,7 @@ const SESSION_FIRST_PLATFORMS = new Set([ const API_KEY_FIRST_PLATFORMS = new Set([ 'openai', + 'orcarouter', 'claude', 'gemini', 'cliproxyapi', diff --git a/src/web/pages/settings.payload-rule-platform-options.test.ts b/src/web/pages/settings.payload-rule-platform-options.test.ts index ff05058..d4b91ae 100644 --- a/src/web/pages/settings.payload-rule-platform-options.test.ts +++ b/src/web/pages/settings.payload-rule-platform-options.test.ts @@ -9,6 +9,7 @@ describe('payload rule platform options', () => { '', 'codex', 'sub2api', + 'orcarouter', 'new-api', 'one-api', 'cliproxyapi', diff --git a/src/web/pages/settings/payloadRuleProtocolOptions.ts b/src/web/pages/settings/payloadRuleProtocolOptions.ts index bdb195f..98d97f7 100644 --- a/src/web/pages/settings/payloadRuleProtocolOptions.ts +++ b/src/web/pages/settings/payloadRuleProtocolOptions.ts @@ -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' }, diff --git a/src/web/pages/token-routes/utils.ts b/src/web/pages/token-routes/utils.ts index 0844b64..0a1fcff 100644 --- a/src/web/pages/token-routes/utils.ts +++ b/src/web/pages/token-routes/utils.ts @@ -29,6 +29,7 @@ export const PLATFORM_ENDPOINT_FALLBACK_MAP: Record = { 'one-hub': ['openai'], 'done-hub': ['openai'], sub2api: ['openai'], + orcarouter: ['openai'], veloera: ['openai'], cliproxyapi: ['openai'], claude: ['anthropic'],