From 340b405829f6c134af963f5983ca5a981843a342 Mon Sep 17 00:00:00 2001 From: Nirupam Bhowmick <48842933+heydryft@users.noreply.github.com> Date: Wed, 28 Oct 2026 22:39:32 +0000 Subject: [PATCH] feat: add Runcrate provider preset --- .../model/__tests__/preset-brands.test.ts | 13 +++++++------ src/shared/domain/__tests__/presets.test.ts | 5 +++-- src/shared/domain/baseurl.ts | 2 +- src/shared/domain/presets.ts | 18 ++++++++++++++++++ 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/renderer/src/settings/pages/model/__tests__/preset-brands.test.ts b/src/renderer/src/settings/pages/model/__tests__/preset-brands.test.ts index ecbaf48b..3e4ae661 100644 --- a/src/renderer/src/settings/pages/model/__tests__/preset-brands.test.ts +++ b/src/renderer/src/settings/pages/model/__tests__/preset-brands.test.ts @@ -1,9 +1,9 @@ /** - * 50 个内置预设各自该显示**谁**的 logo —— 一张钉死的对照表。 + * 51 个内置预设各自该显示**谁**的 logo —— 一张钉死的对照表。 * * **为什么值得单独一个文件:** `brands.test.ts` 守的是规则表本身自洽 * (每个牌子有样本、顺序不打架),但它不知道「添加供应商」目录里到底摆着 - * 哪 50 个名字。而两张表是**分开演进**的:presets.ts 加一家、或者给某家改个 + * 哪 51 个名字。而两张表是**分开演进**的:presets.ts 加一家、或者给某家改个 * 更好听的名字,`brands.ts` 一个字都不用动就能悄悄错掉一个 logo。 * * ★★ 这不是假想。这张表第一次跑出来时,43 家里 **13 家没有图标、3 家挂着 @@ -24,8 +24,8 @@ import { resolveBrand, type Brand } from '../../../../components/brand/brands' /** * `null` = 我们**故意**不给它图标,退回首字母。 - * 七家都是 lobehub 根本没收字形的(逐个在 `@lobehub/icons-static-svg/icons/` - * 下找过):Requesty、Opper、API Route、Cheaper Inference、OhMyGPT、LocalAI,以及 llama.cpp —— 最后这个是 ggerganov 的 + * 八家都是 lobehub 根本没收字形的(逐个在 `@lobehub/icons-static-svg/icons/` + * 下找过):Requesty、Opper、API Route、Cheaper Inference、Runcrate、OhMyGPT、LocalAI,以及 llama.cpp —— 最后这个是 ggerganov 的 * 独立项目,不是 Meta 的东西。 * * ★ 编一个「差不多的」图标比没有图标更糟:用户看见 Meta 的 ∞ 会以为 @@ -87,6 +87,7 @@ const EXPECTED: Readonly> = { 'api-route': null, atlascloud: 'atlascloud', cheaperinference: null, + runcrate: null, siliconflow: 'siliconcloud', 'siliconflow-intl': 'siliconcloud', together: 'together', @@ -129,10 +130,10 @@ describe('内置预设的品牌图标', () => { * 「null 只有这么几家」:某天规则表改窄了让一堆家一起掉进 null, * 这里会红,而界面上只会安静地多出几个字母。 */ - it('没有图标的就是这七家,不多不少', () => { + it('没有图标的就是这八家,不多不少', () => { const blank = PROVIDER_PRESETS.filter((p) => resolveBrand(p.name, p.id) === null).map( (p) => p.id ) - expect(blank).toEqual(['requesty', 'opper', 'api-route', 'cheaperinference', 'ohmygpt', 'llamacpp', 'localai']) + expect(blank).toEqual(['requesty', 'opper', 'api-route', 'cheaperinference', 'runcrate', 'ohmygpt', 'llamacpp', 'localai']) }) }) diff --git a/src/shared/domain/__tests__/presets.test.ts b/src/shared/domain/__tests__/presets.test.ts index cff02a27..c13501d8 100644 --- a/src/shared/domain/__tests__/presets.test.ts +++ b/src/shared/domain/__tests__/presets.test.ts @@ -257,6 +257,7 @@ describe('预设表 × joinUpstreamUrl:拼出来的 URL 没有畸形', () => { ['requesty', 'anthropic', 'https://router.requesty.ai/v1/messages'], ['atlascloud', 'anthropic', 'https://api.atlascloud.ai/v1/messages'], ['cheaperinference', 'anthropic', 'https://api.cheaperinference.com/v1/messages'], + ['runcrate', 'anthropic', 'https://api.runcrate.ai/v1/messages'], ['opper', 'openai-chat', 'https://api.opper.ai/v3/compat/chat/completions'], ['opper', 'anthropic', 'https://api.opper.ai/v3/compat/v1/messages'], ['kimi-coding', 'anthropic', 'https://api.kimi.com/coding/v1/messages'], @@ -349,12 +350,12 @@ describe('预设表 · 模型列表', () => { }) }) - it('实测免鉴权的那六家标了 modelListPublic', () => { + it('实测免鉴权的那七家标了 modelListPublic', () => { const publics = PROVIDER_PRESETS.filter((p) => p.endpoints.some((e) => e.modelListPublic === true) ).map((p) => p.id) expect(new Set(publics)).toEqual( - new Set(['openrouter', 'requesty', 'atlascloud', 'deepinfra', 'opencode-go', 'ollama-cloud']) + new Set(['openrouter', 'requesty', 'atlascloud', 'deepinfra', 'opencode-go', 'ollama-cloud', 'runcrate']) ) }) }) diff --git a/src/shared/domain/baseurl.ts b/src/shared/domain/baseurl.ts index 1e6102bd..2d3a69cf 100644 --- a/src/shared/domain/baseurl.ts +++ b/src/shared/domain/baseurl.ts @@ -46,7 +46,7 @@ export function joinUpstreamUrl(baseUrl: string, path: string): string { * * | 形状 | 厂商 | * |---|---| - * | `/v1` | OpenAI、Moonshot、MiniMax、混元、StepFun、百川、Mistral、xAI、Together、硅基流动、Requesty、Atlas Cloud、Cheaper Inference | + * | `/v1` | OpenAI、Moonshot、MiniMax、混元、StepFun、百川、Mistral、xAI、Together、硅基流动、Requesty、Atlas Cloud、Cheaper Inference、Runcrate | * | 别的前缀 | Groq `/openai/v1`、Fireworks `/inference/v1`、**DeepInfra `/v1/openai`**、OpenRouter `/api/v1`、**智谱 `/api/paas/v4`**、**火山 `/api/v3`**、百炼 `/compatible-mode/v1`、**千帆 `/v2`**、Cohere `/compatibility/v1`、Opper `/v3/compat` | * | 尾斜杠 | **Gemini `/v1beta/openai/`** | * diff --git a/src/shared/domain/presets.ts b/src/shared/domain/presets.ts index 1885577f..521c506d 100644 --- a/src/shared/domain/presets.ts +++ b/src/shared/domain/presets.ts @@ -966,6 +966,24 @@ export const PROVIDER_PRESETS: readonly ProviderPreset[] = [ '同一个 key 也可走 OpenAI Responses 与 Anthropic 协议。', verification: 'probed' }, + { + id: 'runcrate', + name: 'Runcrate', + category: 'aggregator', + endpoints: [ + oa('https://api.runcrate.ai/v1', true, true), + resp('https://api.runcrate.ai/v1', true), + anth('https://api.runcrate.ai') + ], + docsUrl: 'https://www.runcrate.ai/docs/models/overview', + apiKeyUrl: 'https://www.runcrate.ai/dashboard/api-keys', + suggestedModels: ['deepseek-ai/DeepSeek-V4.1-Flash', 'zai-org/GLM-5.3', 'moonshotai/Kimi-K2.6'], + notes: + '模型 ID 带厂商前缀且区分大小写(如 deepseek-ai/DeepSeek-V4.1-Flash),请使用模型列表返回的完整 ID。' + + '模型列表免鉴权就能拉,没填 key 也能先看有哪些模型(因此列表能拉到不代表 key 有效)。' + + '同一个 key 也可走 OpenAI Responses 与 Anthropic 协议。', + verification: 'probed' + }, { id: 'siliconflow', name: '硅基流动(国内)',