feat: Xiaomi MiMo Token Plan provider + per-connection API protocol selector (#8861)

* feat(sse): add alternateFormats registry field and resolver

* feat(sse): honor per-connection targetFormat in getTargetFormat

Registry-driven format lookup now resolves an alternate protocol
declared for the provider when the connection's providerSpecificData
carries a matching targetFormat, falling back to the entry's default
format otherwise.

* feat(sse): resolve base URL from selected alternate format

resolveBaseUrl now falls back to the connection's selected alternate
protocol (providerSpecificData.targetFormat) before the provider's
default base URL, while a manual providerSpecificData.baseUrl override
still wins over both.

* feat(sse): apply alternate format auth header and extra headers

DefaultExecutor's registry authHeader lookup and BaseExecutor's shared
header preamble now both honor a connection's selected alternate
protocol: the alternate's authHeader wins over the registry default,
and its extra headers (e.g. Anthropic-Version) are merged in.

* refactor(sse): extract resolveAlternate helper into BaseExecutor

Centralizes the getRegistryEntry() + resolveAlternateFormat() pair
that resolveBaseUrl, buildHeadersPreamble, and DefaultExecutor's
authHeader lookup each duplicated, so a future call-site can't diverge
from the shared precedence. Also translates the PT-BR comments added
in the previous three commits to match the surrounding English. Pure
refactor — no behavior change.

* feat(sse): declare Anthropic-compatible variant for xiaomi-mimo

The provider publishes the same catalog over /anthropic/v1/messages on the same
host. Selecting it also required bypassing the per-provider URL normalizers in
DefaultExecutor.buildUrl(): normalizeXiaomiMimoChatUrl() appends /chat/completions
unconditionally, which mangled the alternate's already-complete endpoint into
.../anthropic/v1/messages/chat/completions.

* feat(sse): add xiaomi-mimo-token-plan provider with monthly quota

Token Plan is a separate product: tp- keys authenticate only on the regional
token-plan-sgp host and return 401 on api.xiaomimimo.com, where the existing
xiaomi-mimo provider points. Same pattern as qwen-cloud-token-plan.

Registers the monthly token allowance (no balance API upstream) and declares
the Anthropic-compatible variant on the token-plan host.

* feat(dashboard): add API protocol selector to connection modal

Providers that declare alternateFormats in the registry now expose an opt-in
protocol dropdown on the connection modal. The choice persists to
providerSpecificData.targetFormat as an explicit null when set back to the
default, since the PUT route merges { ...existing, ...incoming } and an omitted
key would keep the previous override.

* fix(i18n,quality): vi parity for the protocol selector + own-growth rebaselines

The three new provider keys landed only in en/pt-BR, so the vi parity test failed
(tests/unit/i18n-vi-completeness.test.ts asserts key parity AND no __MISSING__
markers — running i18n:sync-ui would have satisfied the first and broken the
second). Added translated values instead. Scoped to vi: it and pt-BR are the only
locales with a parity test.

Rebaselines are this PR's own growth: EditConnectionModal.tsx 1283->1316 (the
selector field) and open-sse/executors/base.ts 1540->1562 (alternate-format
resolution at the existing buildUrl/headers chokepoint).

Adds the changelog fragment.

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-28 12:56:12 -03:00
committed by GitHub
parent ec0edef499
commit 85b9c1754e
20 changed files with 631 additions and 10 deletions

View File

@@ -0,0 +1 @@
- **Providers**: adds the Xiaomi MiMo Token Plan provider and a per-connection API-protocol selector — some providers publish the same models over more than one protocol, and a connection can now pick the alternative instead of being locked to the OpenAI-compatible default

View File

@@ -342,7 +342,7 @@
"_rebaseline_pr1043_minimax_tts": "Upstream port decolua/9router#1043 (toanalien) own growth: audioSpeech.ts 965->1061 (+96). Adds MiniMax T2A v2 TTS dispatch (handleMinimaxSpeech + hexToBytes helper) — provider entry was already in audioRegistry (format: minimax-tts) but no handler existed, falling through to the OpenAI-compatible default that fails (T2A has custom shape + hex-encoded audio + base_resp envelope). New branch sits next to the other inline provider branches (xiaomi-mimo, coqui, tortoise, aws-polly) — extracting would just create indirection. Covered by tests/unit/minimax-tts-1043.test.ts (3 tests, GREEN: success, base_resp error, invalid-hex).",
"_rebaseline_pr4592_exclude_exhausted_auto": "Reconcile #4592 already-merged growth: combo.ts 2991->3036 (+45, terminal-status quota-cutoff exclusion in buildAutoCandidates + opt-in gate). Fast-gate PR->release does not run check:file-size.",
"open-sse/executors/antigravity.ts": 1528,
"open-sse/executors/base.ts": 1540,
"open-sse/executors/base.ts": 1562,
"open-sse/executors/chatgpt-web.ts": 3241,
"open-sse/executors/codex.ts": 1534,
"open-sse/executors/cursor.ts": 1560,
@@ -375,7 +375,7 @@
"src/app/(dashboard)/dashboard/costs/quota-share/components/PoolWizard.tsx": 1022,
"src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.tsx": 2615,
"src/app/(dashboard)/dashboard/health/page.tsx": 1165,
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx": 1283,
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx": 1316,
"src/app/(dashboard)/dashboard/providers/page.tsx": 1923,
"src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": 1201,
"src/app/(dashboard)/dashboard/settings/components/PricingTab.tsx": 1019,
@@ -410,5 +410,6 @@
"_rebaseline_2026_07_27_v3849_train3": "Merge-train 3 (13 PRs) — owner-approved 2026-07-27. Both entries are genuine irreducible growth at existing chokepoints, not new branches: src/lib/db/apiKeys.ts 1518->1529 (#8805 cx/* ≡ codex/* API-key model permissions); open-sse/handlers/chatCore.ts 5006->5020 (#8806 real response payload into plugin onResponse hooks). Covered by tests/unit/db-apiKeys-crud.test.ts (4 new cases) and the two plugin-hook test files updated in #8806 respectively.",
"_rebaseline_2026_07_28_8842_antigravity_projectid_refresh": "PR #8842 (fix/antigravity-projectid-refresh) own growth: open-sse/executors/antigravity.ts 1493->1528 (+35 = projectId discovery in refreshCredentials: import ensureAntigravityProjectAssigned + trim projectId + call ensureAntigravityProjectAssigned with 8s timeout + persistDiscoveredAntigravityProjectId + log success/failure). Irreducible wiring at the existing credential-refresh chokepoint. Covered by tests/unit/executor-antigravity.test.ts (4 new test cases).",
"_rebaseline_2026_07_28_8842_antigravity_test": "PR #8842 test growth: tests/unit/executor-antigravity.test.ts new testFrozen 1098 (4 new test cases for projectId discovery during refresh: discovers when empty, skips when set, handles failure, skips when access_token not a string). All above cap 1000 on day one.",
"_rebaseline_2026_07_28_8860_tokenrefresh_projectid": "PR #8860 (fix/antigravity-projectid-centralized) own test growth: tests/unit/token-refresh-service.test.ts 1311->1378 (+67 = 4 cases covering projectId discovery on the tokenRefresh.ts path — the Dashboard/health-check refresh route, which #8842 did not reach since that fixed the executor path). Covered by the same file."
"_rebaseline_2026_07_28_8860_tokenrefresh_projectid": "PR #8860 (fix/antigravity-projectid-centralized) own test growth: tests/unit/token-refresh-service.test.ts 1311->1378 (+67 = 4 cases covering projectId discovery on the tokenRefresh.ts path — the Dashboard/health-check refresh route, which #8842 did not reach since that fixed the executor path). Covered by the same file.",
"_rebaseline_2026_07_28_8861_xiaomi_token_plan": "PR #8861 (feat/xiaomi-token-plan-protocol-selector) own growth: EditConnectionModal.tsx 1283->1316 (+33 = the per-connection API-protocol selector field) and open-sse/executors/base.ts 1540->1562 (+22 = alternate-format resolution at the existing buildUrl/headers chokepoint). Both are irreducible wiring at existing call sites."
}

View File

@@ -0,0 +1,35 @@
/**
* Protocolos alternativos declarados por um provedor.
*
* Alguns upstreams publicam o mesmo catalogo em mais de um protocolo (ex.: Xiaomi
* MiMo expoe /v1/chat/completions e /anthropic/v1/messages no mesmo host). Trocar
* de protocolo troca URL, header de auth e headers extras — por isso a alternativa
* carrega os tres, nao so a URL.
*
* A escolha vive na conexao, em providerSpecificData.targetFormat, e so vale se
* casar com uma alternativa declarada: valor desconhecido cai no formato padrao,
* nunca produz uma URL/auth orfas.
*/
import type { RegistryEntry } from "./shared.ts";
export interface AlternateFormat {
format: string;
baseUrl: string;
chatPath?: string;
authHeader?: string;
headers?: Record<string, string>;
urlSuffix?: string;
label: string;
}
export function resolveAlternateFormat(
entry: RegistryEntry | null | undefined,
providerSpecificData: unknown
): AlternateFormat | null {
const alternates = entry?.alternateFormats;
if (!alternates?.length) return null;
const requested = (providerSpecificData as { targetFormat?: unknown } | null | undefined)
?.targetFormat;
if (typeof requested !== "string" || !requested) return null;
return alternates.find((alt) => alt.format === requested) ?? null;
}

View File

@@ -35,6 +35,7 @@ import { bedrockProvider } from "./registry/bedrock/index.ts";
import { inner_aiProvider } from "./registry/inner-ai/index.ts";
import { qoderProvider } from "./registry/qoder/index.ts";
import { xiaomi_mimoProvider } from "./registry/xiaomi-mimo/index.ts";
import { xiaomi_mimo_token_planProvider } from "./registry/xiaomi-mimo-token-plan/index.ts";
import { codestralProvider } from "./registry/codestral/index.ts";
import { wandbProvider } from "./registry/wandb/index.ts";
import { predibaseProvider } from "./registry/predibase/index.ts";
@@ -254,6 +255,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
"inner-ai": inner_aiProvider,
qoder: qoderProvider,
"xiaomi-mimo": xiaomi_mimoProvider,
"xiaomi-mimo-token-plan": xiaomi_mimo_token_planProvider,
codestral: codestralProvider,
wandb: wandbProvider,
predibase: predibaseProvider,

View File

@@ -0,0 +1,29 @@
import type { RegistryEntry } from "../../shared.ts";
import { CHAT_OPENAI_COMPAT_MODELS, getAnthropicCompatHeaders } from "../../shared.ts";
/**
* Xiaomi MiMo Token Plan (platform.xiaomimimo.com/token-plan).
*
* A separate product from the regular account: `tp-…` keys authenticate only on
* the regional token-plan host and return 401 on api.xiaomimimo.com. Same pattern
* already used by qwen-cloud-token-plan, which is likewise its own entry.
*/
export const xiaomi_mimo_token_planProvider: RegistryEntry = {
id: "xiaomi-mimo-token-plan",
alias: "mimotp",
format: "openai",
executor: "default",
baseUrl: "https://token-plan-sgp.xiaomimimo.com/v1",
authType: "apikey",
authHeader: "bearer",
models: CHAT_OPENAI_COMPAT_MODELS["xiaomi-mimo-token-plan"],
alternateFormats: [
{
format: "claude",
baseUrl: "https://token-plan-sgp.xiaomimimo.com/anthropic/v1/messages",
authHeader: "x-api-key",
headers: getAnthropicCompatHeaders(),
label: "Anthropic-compatible",
},
],
};

View File

@@ -1,5 +1,5 @@
import type { RegistryEntry } from "../../shared.ts";
import { CHAT_OPENAI_COMPAT_MODELS } from "../../shared.ts";
import { CHAT_OPENAI_COMPAT_MODELS, getAnthropicCompatHeaders } from "../../shared.ts";
export const xiaomi_mimoProvider: RegistryEntry = {
id: "xiaomi-mimo",
@@ -10,4 +10,16 @@ export const xiaomi_mimoProvider: RegistryEntry = {
authType: "apikey",
authHeader: "bearer",
models: CHAT_OPENAI_COMPAT_MODELS["xiaomi-mimo"],
// Xiaomi also publishes the same catalog over an Anthropic-compatible endpoint
// on the same host. The operator picks per connection; see
// config/providers/alternateFormats.ts.
alternateFormats: [
{
format: "claude",
baseUrl: "https://api.xiaomimimo.com/anthropic/v1/messages",
authHeader: "x-api-key",
headers: getAnthropicCompatHeaders(),
label: "Anthropic-compatible",
},
],
};

View File

@@ -176,6 +176,12 @@ export interface RegistryEntry {
* standard OpenAI array-shaped content untouched (see openai-responses.ts).
*/
requiresPlainStringContent?: boolean;
/**
* Protocolos alternativos que este provedor aceita (ex.: um endpoint
* Anthropic-compatible alem do OpenAI-compatible padrao). A conexao escolhe
* via providerSpecificData.targetFormat; ver config/providers/alternateFormats.ts.
*/
alternateFormats?: import("./alternateFormats.ts").AlternateFormat[];
}
/**
@@ -403,6 +409,10 @@ export const CHAT_OPENAI_COMPAT_MODELS: Record<string, RegistryModel[]> = {
{ id: "mimo-v2.5-pro", name: "MiMo-V2.5-Pro", contextLength: 1048576, maxOutputTokens: 131072 },
{ id: "mimo-v2.5", name: "MiMo-V2.5", contextLength: 1048576, maxOutputTokens: 131072 },
],
"xiaomi-mimo-token-plan": [
{ id: "mimo-v2.5-pro", name: "MiMo-V2.5-Pro", contextLength: 1048576, maxOutputTokens: 131072 },
{ id: "mimo-v2.5", name: "MiMo-V2.5", contextLength: 1048576, maxOutputTokens: 131072 },
],
gitlawb: [
{ id: "mimo-v2.5-pro", name: "MiMo-V2.5-Pro", contextLength: 1048576, maxOutputTokens: 131072 },
{ id: "mimo-v2.5", name: "MiMo-V2.5", contextLength: 1048576, maxOutputTokens: 131072 },

View File

@@ -1,4 +1,9 @@
import { HTTP_STATUS, FETCH_TIMEOUT_MS } from "../config/constants.ts";
import { getRegistryEntry } from "../config/providerRegistry.ts";
import {
resolveAlternateFormat,
type AlternateFormat,
} from "../config/providers/alternateFormats.ts";
import {
CLAUDE_CLI_BILLING_VERSION,
CLAUDE_CLI_STAINLESS_RUNTIME_VERSION,
@@ -420,8 +425,23 @@ export class BaseExecutor {
*/
protected resolveBaseUrl(credentials: ProviderCredentials | null, fallback?: string): string {
const psdBaseUrl = credentials?.providerSpecificData?.baseUrl;
return (
(typeof psdBaseUrl === "string" ? psdBaseUrl : "") || fallback || this.config.baseUrl || ""
// Operator's manual override always wins (#6147).
if (typeof psdBaseUrl === "string" && psdBaseUrl) return psdBaseUrl;
// An alternate protocol selected on the connection carries its own URL.
const alternate = this.resolveAlternate(credentials);
if (alternate?.baseUrl) return alternate.baseUrl;
return fallback || this.config.baseUrl || "";
}
/**
* Alternate protocol selected on this connection, if the provider declares one
* that matches. Centralizes the registry lookup so every call-site resolves the
* same way.
*/
protected resolveAlternate(credentials: ProviderCredentials | null): AlternateFormat | null {
return resolveAlternateFormat(
getRegistryEntry(this.provider),
credentials?.providerSpecificData
);
}
@@ -463,9 +483,11 @@ export class BaseExecutor {
credentials: ProviderCredentials,
stream: boolean
): { headers: Record<string, string>; effectiveKey: string | undefined } {
const alternate = this.resolveAlternate(credentials);
const headers: Record<string, string> = {
"Content-Type": "application/json",
...this.config.headers,
...(alternate?.headers || {}),
};
// Allow per-provider User-Agent override via environment variable.

View File

@@ -139,6 +139,21 @@ export class DefaultExecutor extends BaseExecutor {
const normalized = baseUrl.replace(/\/$/, "");
return `${normalized}${customPath || "/messages"}`;
}
// An alternate protocol selected on the connection carries a complete endpoint
// URL, so it must bypass the per-provider normalizers in the switch below —
// those assume the provider's default (OpenAI-shaped) path and would mangle it,
// e.g. appending "/chat/completions" to an Anthropic ".../v1/messages" endpoint.
{
const alternate = this.resolveAlternate(credentials);
const manualBaseUrl = credentials?.providerSpecificData?.baseUrl;
const hasManualBaseUrl = typeof manualBaseUrl === "string" && !!manualBaseUrl;
if (alternate?.baseUrl && !hasManualBaseUrl) {
// Operator's manual override (#6147) keeps its own semantics and falls
// through to the provider-specific handling below.
const normalized = alternate.baseUrl.replace(/\/$/, "");
return `${normalized}${alternate.chatPath || ""}${alternate.urlSuffix || ""}`;
}
}
switch (this.provider) {
case "openai": {
// #5842: responses-only models (o1-pro / gpt-5.x-pro) 404 on
@@ -222,7 +237,8 @@ export class DefaultExecutor extends BaseExecutor {
const baseUrl = this.resolveBaseUrl(credentials);
return normalizeSapChatUrl(baseUrl);
}
case "xiaomi-mimo": {
case "xiaomi-mimo":
case "xiaomi-mimo-token-plan": {
const baseUrl = this.resolveBaseUrl(credentials);
return normalizeXiaomiMimoChatUrl(baseUrl);
}
@@ -443,9 +459,12 @@ export class DefaultExecutor extends BaseExecutor {
headers["anthropic-version"] = "2023-06-01";
}
} else {
// Use registry authHeader if available, otherwise default to bearer
// Use registry authHeader if available, otherwise default to bearer.
// An alternate protocol selected on the connection carries its own auth
// scheme (e.g. claude uses x-api-key where openai uses bearer).
const entry = getRegistryEntry(this.provider);
const authHeader = entry?.authHeader || "bearer";
const alternate = this.resolveAlternate(credentials);
const authHeader = alternate?.authHeader || entry?.authHeader || "bearer";
const token = effectiveKey || credentials.accessToken || entry?.anonymousApiKey;
if (token) {
if (authHeader === "x-api-key") {

View File

@@ -1,6 +1,7 @@
// @ts-nocheck
import { PROVIDERS } from "../config/constants.ts";
import { getRegistryEntry } from "../config/providerRegistry.ts";
import { resolveAlternateFormat } from "../config/providers/alternateFormats.ts";
import {
buildClaudeCodeCompatibleHeaders,
CLAUDE_CODE_COMPATIBLE_DEFAULT_CHAT_PATH,
@@ -437,7 +438,13 @@ export function getTargetFormat(provider, providerSpecificData = null) {
}
// Registry-driven format lookup
const entry = getRegistryEntry(provider);
if (entry) return entry.format || "openai";
if (entry) {
// Per-connection override (providerSpecificData.targetFormat), only valid
// when it matches an alternate declared by the provider.
const alternate = resolveAlternateFormat(entry, providerSpecificData);
if (alternate) return alternate.format;
return entry.format || "openai";
}
const config = getProviderConfig(provider);
return config.format || "openai";
}

View File

@@ -24,6 +24,7 @@ import { resolveDashboardProviderInfo } from "../../../providerPageUtils";
import {
isBaseUrlConfigurableProvider,
isBaseUrlOverrideEligibleProvider,
getAlternateFormats,
getProviderBaseUrlDefault,
getProviderBaseUrlHint,
getProviderBaseUrlPlaceholder,
@@ -113,6 +114,7 @@ export default function EditConnectionModal({
apiKey: "",
healthCheckInterval: 60,
baseUrl: "",
targetFormat: "",
cx: "",
region: "",
apiRegion: "international",
@@ -178,6 +180,9 @@ export default function EditConnectionModal({
connectionProviderSpecificData.baseUrl.trim().length > 0
);
const usesBaseUrl = isConfigurableBaseUrl || (isBaseUrlOverrideEligible && showBaseUrlOverride);
// Protocol selector: only for providers that declare alternatives.
const alternateFormats = getAlternateFormats(provider);
const showProtocolSelector = alternateFormats.length > 0;
const defaultBaseUrl = getProviderBaseUrlDefault(provider);
const isVertex = provider === "vertex" || provider === "vertex-partner";
const { defaultRegion, showsRegion } = getProviderRegionConfig(provider);
@@ -237,6 +242,7 @@ export default function EditConnectionModal({
if (isOpen && connection) {
const effectiveProvider = connection.provider || providerId;
const existingBaseUrl = stringField(connection.providerSpecificData?.baseUrl);
const existingTargetFormat = stringField(connection.providerSpecificData?.targetFormat);
const existingRegion = stringField(connection.providerSpecificData?.region);
const existingCustomUserAgent = stringField(connection.providerSpecificData?.customUserAgent);
const existingOpenRouterPreset = stringField(connection.providerSpecificData?.preset);
@@ -290,6 +296,7 @@ export default function EditConnectionModal({
apiKey: "",
healthCheckInterval: connection.healthCheckInterval ?? 60,
baseUrl: existingBaseUrl || defaultBaseUrl,
targetFormat: existingTargetFormat || "",
cx: existingCx,
region: existingRegion || (showsRegion ? defaultRegion : ""),
apiRegion: (connection.providerSpecificData?.apiRegion as string) || "international",
@@ -589,6 +596,12 @@ export default function EditConnectionModal({
}
if (updates.providerSpecificData) {
updates.providerSpecificData.disableCooling = formData.disableCooling ? true : undefined;
// Explicit `null`, not `undefined`: the PUT route merges
// { ...existing, ...incoming }, so omitting the key would keep the previous
// choice and switching back to the default would never take effect.
if (showProtocolSelector) {
updates.providerSpecificData.targetFormat = formData.targetFormat || null;
}
}
const freeOnlyChanged =
showFreeModelsToggle &&
@@ -1035,6 +1048,26 @@ export default function EditConnectionModal({
/>
)}
{showProtocolSelector && (
<Select
label={providerText(t, "apiProtocolLabel", "API protocol")}
value={formData.targetFormat}
options={[
{
value: "",
label: providerText(t, "apiProtocolDefault", "OpenAI-compatible (default)"),
},
...alternateFormats.map((alt) => ({ value: alt.format, label: alt.label })),
]}
onChange={(e) => setFormData({ ...formData, targetFormat: e.target.value })}
hint={providerText(
t,
"apiProtocolHint",
"Some providers publish the same models over more than one protocol. Leave the default unless you need the alternative."
)}
/>
)}
<ProviderRegionField
provider={provider}
value={formData.region}

View File

@@ -17,6 +17,8 @@ import {
} from "@/lib/providers/requestDefaults";
import { type CodexGlobalServiceMode } from "@/lib/providers/codexFastTier";
import { CC_COMPATIBLE_DEFAULT_CHAT_PATH } from "./providerDetailConstants";
import { getRegistryEntry } from "@omniroute/open-sse/config/providerRegistry";
import type { AlternateFormat } from "@omniroute/open-sse/config/providers/alternateFormats";
import {
type ProviderMessageTranslator,
providerText,
@@ -282,6 +284,16 @@ export function isBaseUrlOverrideEligibleProvider(providerId?: string | null): b
return true;
}
/**
* Alternate API protocols the provider declares in the registry (e.g. an
* Anthropic-compatible endpoint alongside the default OpenAI one). An empty list
* means the protocol selector stays hidden for this provider.
*/
export function getAlternateFormats(providerId?: string | null): AlternateFormat[] {
if (!providerId) return [];
return getRegistryEntry(providerId)?.alternateFormats ?? [];
}
export function getProviderBaseUrlDefault(providerId?: string | null) {
const localProvider = getLocalProviderMetadata(providerId);
if (typeof localProvider?.localDefault === "string" && localProvider.localDefault.trim()) {

View File

@@ -6006,6 +6006,9 @@
"doubaoWebDesc": "ByteDance AI chat via dola.com",
"overrideBaseUrlAdvanced": "Advanced: override base URL",
"overrideBaseUrlHint": "Advanced: point this built-in provider at a custom endpoint. Leave blank to use the default.",
"apiProtocolLabel": "API protocol",
"apiProtocolDefault": "OpenAI-compatible (default)",
"apiProtocolHint": "Some providers publish the same models over more than one protocol. Leave the default unless you need the alternative.",
"bulkAddFormatHintCloudflare": "One key per line. Format: name|accountId|apiKey (Cloudflare account ID + API token).",
"lmarenaWebCookieHint": "Open arena.ai, sign in, then copy the full Cookie header from a Network request. Include arena-auth-prod-v1.0 and arena-auth-prod-v1.1 (and further chunks if present), preferably with cf_clearance. Do not paste only the empty arena-auth-prod-v1 cookie. Optional: providerSpecificData.recaptchaV3Token if create-evaluation still returns 403.",
"kimiOfficialSupporterBadge": "Founding Friend",

View File

@@ -5994,6 +5994,9 @@
"doubaoWebDesc": "Chat de IA da ByteDance via doubao.com",
"overrideBaseUrlAdvanced": "Avançado: sobrescrever URL base",
"overrideBaseUrlHint": "Avançado: aponta este provedor embutido para um endpoint personalizado. Deixe em branco para usar o padrão.",
"apiProtocolLabel": "Protocolo da API",
"apiProtocolDefault": "OpenAI-compatible (padrão)",
"apiProtocolHint": "Alguns provedores publicam os mesmos modelos em mais de um protocolo. Mantenha o padrão a menos que precise da alternativa.",
"bulkAddFormatHintCloudflare": "Uma chave por linha. Formato: nome|accountId|apiKey (ID de conta Cloudflare + token de API).",
"lmarenaWebCookieHint": "Abra arena.ai, faça login e depois copie o cabeçalho Cookie completo de uma requisição de rede. Inclua arena-auth-prod-v1.0 e arena-auth-prod-v1.1 (e outros fragmentos, se houver), preferencialmente com cf_clearance. Não cole apenas o cookie vazio arena-auth-prod-v1. Opcional: providerSpecificData.recaptchaV3Token se create-evaluation ainda retornar 403.",
"kimiOfficialSupporterBadge": "Amigo Fundador",

View File

@@ -5994,6 +5994,9 @@
"doubaoWebDesc": "Trò chuyện AI của ByteDance qua dola.com",
"overrideBaseUrlAdvanced": "Nâng cao: ghi đè URL cơ sở",
"overrideBaseUrlHint": "Nâng cao: trỏ nhà cung cấp tích hợp này đến một endpoint tùy chỉnh. Để trống để sử dụng mặc định.",
"apiProtocolLabel": "Giao thức API",
"apiProtocolDefault": "Tương thích OpenAI (mặc định)",
"apiProtocolHint": "Một số nhà cung cấp phát hành cùng các mô hình qua nhiều giao thức khác nhau. Hãy giữ giá trị mặc định trừ khi bạn cần dùng giao thức thay thế.",
"bulkAddFormatHintCloudflare": "Mỗi dòng một khóa. Định dạng: name|accountId|apiKey (Cloudflare account ID + API token).",
"lmarenaWebCookieHint": "Mở arena.ai, đăng nhập, sau đó sao chép toàn bộ header Cookie từ một yêu cầu mạng. Bao gồm arena-auth-prod-v1.0 và arena-auth-prod-v1.1 (và các phần tiếp theo nếu có), ưu tiên kèm theo cf_clearance. Không chỉ dán cookie arena-auth-prod-v1 trống. Tùy chọn: providerSpecificData.recaptchaV3Token nếu create-evaluation vẫn trả về 403.",
"kimiOfficialSupporterBadge": "Người Bạn Sáng Lập",

View File

@@ -80,6 +80,13 @@ const KNOWN_PLANS: Record<string, KnownPlanShape> = {
provider: "xiaomi-mimo",
dimensions: [{ unit: "tokens", window: "monthly", limit: 4_100_000_000 }],
},
// Xiaomi MiMo Token Plan (token-plan-sgp): same MONTHLY allowance with no
// balance API as the regular account plan. Adjust in the Wizard's "Limite"
// step when the connection is on a different plan.
"xiaomi-mimo-token-plan": {
provider: "xiaomi-mimo-token-plan",
dimensions: [{ unit: "tokens", window: "monthly", limit: 4_100_000_000 }],
},
alibaba: {
provider: "alibaba",
dimensions: [{ unit: "requests", window: "monthly", limit: 90_000 }],

View File

@@ -438,6 +438,7 @@ export const USAGE_SUPPORTED_PROVIDERS = [
"nanogpt",
"deepseek",
"xiaomi-mimo",
"xiaomi-mimo-token-plan",
"vertex",
"vertex-partner",
"codebuddy-cn",

View File

@@ -217,6 +217,15 @@ export const APIKEY_PROVIDERS_REGIONAL = {
textIcon: "MM",
website: "https://mimo.mi.com",
},
"xiaomi-mimo-token-plan": {
id: "xiaomi-mimo-token-plan",
alias: "mimotp",
name: "Xiaomi MiMo Token Plan",
icon: "devices",
color: "#EA580C",
textIcon: "MT",
website: "https://mimo.mi.com",
},
baidu: {
id: "baidu",
alias: "baidu",

View File

@@ -0,0 +1,185 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import { resolveAlternateFormat } from "../../open-sse/config/providers/alternateFormats.ts";
import type { RegistryEntry } from "../../open-sse/config/providers/shared.ts";
import { getTargetFormat } from "../../open-sse/services/provider.ts";
import { DefaultExecutor } from "../../open-sse/executors/default.ts";
import { getAlternateFormats } from "../../src/app/(dashboard)/dashboard/providers/[id]/providerPageHelpers.ts";
const ENTRY: RegistryEntry = {
id: "demo",
format: "openai",
executor: "default",
authType: "apikey",
authHeader: "bearer",
baseUrl: "https://example.com/v1",
models: [],
alternateFormats: [
{
format: "claude",
baseUrl: "https://example.com/anthropic/v1/messages",
authHeader: "x-api-key",
headers: { "Anthropic-Version": "2023-06-01" },
label: "Anthropic-compatible",
},
],
};
test("retorna a alternativa quando targetFormat da conexao casa", () => {
const alt = resolveAlternateFormat(ENTRY, { targetFormat: "claude" });
assert.equal(alt?.format, "claude");
assert.equal(alt?.authHeader, "x-api-key");
});
test("retorna null quando targetFormat nao casa com nenhuma alternativa", () => {
assert.equal(resolveAlternateFormat(ENTRY, { targetFormat: "gemini" }), null);
});
test("retorna null quando a conexao nao tem targetFormat", () => {
assert.equal(resolveAlternateFormat(ENTRY, {}), null);
assert.equal(resolveAlternateFormat(ENTRY, null), null);
assert.equal(resolveAlternateFormat(ENTRY, undefined), null);
});
test("retorna null quando a entry nao declara alternativas", () => {
assert.equal(resolveAlternateFormat({ ...ENTRY, alternateFormats: undefined }, { targetFormat: "claude" }), null);
assert.equal(resolveAlternateFormat(null, { targetFormat: "claude" }), null);
});
test("ignora targetFormat que nao e string nao-vazia", () => {
assert.equal(resolveAlternateFormat(ENTRY, { targetFormat: 42 }), null);
assert.equal(resolveAlternateFormat(ENTRY, { targetFormat: "" }), null);
});
// Task 2: getTargetFormat honra o override da conexao.
test("getTargetFormat: sem override da conexao usa o formato padrao do registry", () => {
assert.equal(getTargetFormat("xiaomi-mimo", null), "openai");
});
test("getTargetFormat: override da conexao troca o formato quando declarado", () => {
assert.equal(getTargetFormat("xiaomi-mimo", { targetFormat: "claude" }), "claude");
});
test("getTargetFormat: override desconhecido cai no formato padrao", () => {
assert.equal(getTargetFormat("xiaomi-mimo", { targetFormat: "gemini" }), "openai");
});
// Task 3: resolucao de base URL honra a alternativa.
//
// Reproduz a precedencia implementada em BaseExecutor.resolveBaseUrl. Nao
// instanciamos o executor real aqui porque getRegistryEntry() le do registry
// estatico de providers (chaveado por provider id) e nenhuma entry real
// declara alternateFormats ainda (isso e a Task 5) — nao ha como injetar uma
// entry fake no registry sem sair do escopo desta task. O contrato de
// precedencia (manual > alternativa > default) e o mesmo aplicado no
// producao; tests/unit/refactor-resolveBaseUrl.test.ts ja cobre o caminho
// real (instanciando BaseExecutor) para os casos sem alternativa, e
// permanece verde apos a mudanca desta task.
function precedence(psd: unknown, entry: unknown, configBaseUrl: string) {
const psdBaseUrl = (psd as { baseUrl?: unknown } | null)?.baseUrl;
if (typeof psdBaseUrl === "string" && psdBaseUrl) return psdBaseUrl;
const alt = resolveAlternateFormat(entry as never, psd);
if (alt?.baseUrl) return alt.baseUrl;
return configBaseUrl;
}
const ENTRY_WITH_ALT = {
alternateFormats: [
{
format: "claude",
baseUrl: "https://alt.example.com/anthropic/v1/messages",
label: "Anthropic",
},
],
} as never;
test("resolveBaseUrl: baseUrl manual da conexao vence a alternativa", () => {
const url = precedence(
{ baseUrl: "https://manual.example.com/v1", targetFormat: "claude" },
ENTRY_WITH_ALT,
"https://default.example.com/v1"
);
assert.equal(url, "https://manual.example.com/v1");
});
test("resolveBaseUrl: alternativa vence o baseUrl padrao", () => {
const url = precedence({ targetFormat: "claude" }, ENTRY_WITH_ALT, "https://default.example.com/v1");
assert.equal(url, "https://alt.example.com/anthropic/v1/messages");
});
test("resolveBaseUrl: sem override usa o baseUrl padrao", () => {
const url = precedence({}, ENTRY_WITH_ALT, "https://default.example.com/v1");
assert.equal(url, "https://default.example.com/v1");
});
// Task 4: auth header e headers extras honram a alternativa.
//
// Mesma limitacao da Task 3: nenhuma entry real do registry declara
// alternateFormats ainda, entao reproduzimos a escolha de authHeader
// implementada em DefaultExecutor.buildHeaders (default.ts) sem instanciar o
// executor inteiro. O teste de regressao abaixo (via DefaultExecutor real)
// cobre o caminho sem alternativa para garantir que a nova chamada a
// resolveAlternateFormat nao alterou o comportamento por-default existente.
function pickAuthHeader(entry: unknown, psd: unknown) {
const alt = resolveAlternateFormat(entry as never, psd);
return alt?.authHeader || (entry as { authHeader?: string } | null)?.authHeader || "bearer";
}
const ENTRY_AUTH = {
authHeader: "bearer",
alternateFormats: [
{
format: "claude",
baseUrl: "https://alt.example.com/anthropic/v1/messages",
authHeader: "x-api-key",
headers: { "Anthropic-Version": "2023-06-01" },
label: "Anthropic",
},
],
} as never;
test("authHeader: formato alternativo troca bearer por x-api-key", () => {
assert.equal(pickAuthHeader(ENTRY_AUTH, { targetFormat: "claude" }), "x-api-key");
});
test("authHeader: sem override mantem o header da entry", () => {
assert.equal(pickAuthHeader(ENTRY_AUTH, {}), "bearer");
});
test("alternativa carrega headers extras do protocolo", () => {
const alt = resolveAlternateFormat(ENTRY_AUTH, { targetFormat: "claude" });
assert.equal(alt?.headers?.["Anthropic-Version"], "2023-06-01");
});
// Regression via producao real: com um targetFormat que NAO casa com nenhuma
// alternativa declarada, buildHeaders cai no authHeader "bearer" padrao da
// entry, provando que a nova chamada a resolveAlternateFormat nao regrediu o
// caminho default. (Ate a Task 5, xiaomi-mimo nao declarava alternateFormats
// nenhuma — agora declara a variante "claude", ver
// tests/unit/xiaomi-providers-registry.test.ts para a cobertura end-to-end
// do caminho COM alternativa, incluindo o caso "claude" que este teste
// cobria antes.)
test("DefaultExecutor.buildHeaders: targetFormat que nao casa mantem o authHeader padrao", () => {
const executor = new DefaultExecutor("xiaomi-mimo");
const headers = executor.buildHeaders(
{
apiKey: "sk-test",
providerSpecificData: { targetFormat: "gemini" },
} as never,
true
) as Record<string, string>;
assert.equal(headers["Authorization"], "Bearer sk-test");
assert.equal(headers["x-api-key"], undefined);
});
test("getAlternateFormats: provedor com alternativas retorna a lista", () => {
const list = getAlternateFormats("xiaomi-mimo");
assert.equal(list.length, 1);
assert.equal(list[0].format, "claude");
});
test("getAlternateFormats: provedor sem alternativas retorna lista vazia", () => {
assert.deepEqual(getAlternateFormats("deepseek"), []);
assert.deepEqual(getAlternateFormats(null), []);
assert.deepEqual(getAlternateFormats(undefined), []);
});

View File

@@ -0,0 +1,227 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import { xiaomi_mimoProvider } from "../../open-sse/config/providers/registry/xiaomi-mimo/index.ts";
import { xiaomi_mimo_token_planProvider } from "../../open-sse/config/providers/registry/xiaomi-mimo-token-plan/index.ts";
import { REGISTRY } from "../../open-sse/config/providers/index.ts";
import { getTargetFormat } from "../../open-sse/services/provider.ts";
import { DefaultExecutor } from "../../open-sse/executors/default.ts";
import { BaseExecutor } from "../../open-sse/executors/base.ts";
import type { ProviderCredentials } from "../../open-sse/executors/base.ts";
import { getKnownPlan } from "../../src/lib/quota/planRegistry.ts";
import { USAGE_SUPPORTED_PROVIDERS } from "../../src/shared/constants/providers.ts";
// ---------------------------------------------------------------------------
// Task 5: xiaomi-mimo declares the Anthropic-compatible variant
// ---------------------------------------------------------------------------
test("xiaomi-mimo aponta para o host da conta normal", () => {
assert.equal(xiaomi_mimoProvider.baseUrl, "https://api.xiaomimimo.com/v1");
assert.equal(xiaomi_mimoProvider.format, "openai");
assert.equal(xiaomi_mimoProvider.authHeader, "bearer");
});
test("xiaomi-mimo declara a variante Anthropic no mesmo host", () => {
const alt = xiaomi_mimoProvider.alternateFormats?.find((a) => a.format === "claude");
assert.ok(alt, "esperava uma alternativa claude declarada");
assert.equal(alt.baseUrl, "https://api.xiaomimimo.com/anthropic/v1/messages");
assert.equal(alt.authHeader, "x-api-key");
assert.equal(alt.headers?.["Anthropic-Version"], "2023-06-01");
assert.ok(alt.label.length > 0);
});
// ---------------------------------------------------------------------------
// Task 5 Step 4: end-to-end coverage via the REAL executors, now that
// xiaomi-mimo actually declares alternateFormats. tests/unit/alternate-formats.test.ts
// only exercises replica functions (precedence/pickAuthHeader) because until this
// task no registry entry declared alternateFormats — that excuse is gone now.
//
// A defect surfaced while writing these tests: DefaultExecutor.buildUrl()'s
// "xiaomi-mimo" case piped resolveBaseUrl() through normalizeXiaomiMimoChatUrl(),
// which unconditionally appends "/chat/completions" — so the alternate's
// already-complete ".../anthropic/v1/messages" URL was mangled into
// ".../anthropic/v1/messages/chat/completions". Fixed in DefaultExecutor.buildUrl()
// (default.ts) with a short-circuit placed after the openai-compatible-*/
// anthropic-compatible-* prefix checks and before `switch (this.provider)`: when
// an alternate format is selected (and no manual providerSpecificData.baseUrl
// override is present, which still wins per #6147), its baseUrl is returned as-is
// — bypassing every per-provider normalizer in the switch, all of which assume the
// provider's default OpenAI-shaped path and would otherwise mangle a
// non-OpenAI-shaped alternate endpoint the same way. Covered end-to-end below via
// the real DefaultExecutor.buildUrl(), not just resolveBaseUrl().
// ---------------------------------------------------------------------------
class TestXiaomiExecutor extends BaseExecutor {
constructor() {
super("xiaomi-mimo", { baseUrl: "https://api.xiaomimimo.com/v1" });
}
publicResolveBaseUrl(credentials: ProviderCredentials | null): string {
return this.resolveBaseUrl(credentials);
}
async transformRequest(model: string, body: unknown, stream: boolean) {
return body;
}
}
test("getTargetFormat: override da conexao troca o formato quando declarado (xiaomi-mimo)", () => {
assert.equal(getTargetFormat("xiaomi-mimo", { targetFormat: "claude" }), "claude");
});
test("DefaultExecutor.buildHeaders: targetFormat claude produz x-api-key + Anthropic-Version", () => {
const executor = new DefaultExecutor("xiaomi-mimo");
const headers = executor.buildHeaders(
{
apiKey: "sk-test",
providerSpecificData: { targetFormat: "claude" },
} as never,
true
) as Record<string, string>;
assert.equal(headers["x-api-key"], "sk-test");
assert.equal(headers["Authorization"], undefined);
assert.equal(headers["Anthropic-Version"], "2023-06-01");
});
test("DefaultExecutor.buildHeaders: sem targetFormat mantem Authorization Bearer (nao-regressao)", () => {
const executor = new DefaultExecutor("xiaomi-mimo");
const headers = executor.buildHeaders(
{
apiKey: "sk-test",
providerSpecificData: {},
} as never,
true
) as Record<string, string>;
assert.equal(headers["Authorization"], "Bearer sk-test");
assert.equal(headers["x-api-key"], undefined);
});
test("resolveBaseUrl: targetFormat claude resolve para o endpoint /anthropic/v1/messages", () => {
const executor = new TestXiaomiExecutor();
const url = executor.publicResolveBaseUrl({
apiKey: "sk-test",
providerSpecificData: { targetFormat: "claude" },
});
assert.equal(url, "https://api.xiaomimimo.com/anthropic/v1/messages");
});
test("resolveBaseUrl: sem targetFormat resolve para o /v1 padrao", () => {
const executor = new TestXiaomiExecutor();
const url = executor.publicResolveBaseUrl({ apiKey: "sk-test", providerSpecificData: {} });
assert.equal(url, "https://api.xiaomimimo.com/v1");
});
test("resolveBaseUrl: providerSpecificData.baseUrl manual vence a alternativa", () => {
const executor = new TestXiaomiExecutor();
const url = executor.publicResolveBaseUrl({
apiKey: "sk-test",
providerSpecificData: {
targetFormat: "claude",
baseUrl: "https://manual.example.com/v1",
},
});
assert.equal(url, "https://manual.example.com/v1");
});
// DefaultExecutor.buildUrl() end-to-end: proves the short-circuit fix in
// default.ts, not just the resolveBaseUrl() layer above.
test("buildUrl: targetFormat claude produz a URL do endpoint Anthropic, sem /chat/completions grudado", () => {
const executor = new DefaultExecutor("xiaomi-mimo");
const url = executor.buildUrl("mimo-v2.5-pro", true, 0, {
apiKey: "sk-x",
providerSpecificData: { targetFormat: "claude" },
});
assert.equal(url, "https://api.xiaomimimo.com/anthropic/v1/messages");
});
test("buildUrl: sem targetFormat mantem a URL OpenAI padrao (nao-regressao)", () => {
const executor = new DefaultExecutor("xiaomi-mimo");
const url = executor.buildUrl("mimo-v2.5-pro", true, 0, {
apiKey: "sk-x",
providerSpecificData: {},
});
assert.equal(url, "https://api.xiaomimimo.com/v1/chat/completions");
});
test("buildUrl: providerSpecificData.baseUrl manual continua vencendo e passa pelo caminho normal", () => {
const executor = new DefaultExecutor("xiaomi-mimo");
const url = executor.buildUrl("mimo-v2.5-pro", true, 0, {
apiKey: "sk-x",
providerSpecificData: {
targetFormat: "claude",
baseUrl: "https://manual.example.com/v1",
},
});
assert.equal(url, "https://manual.example.com/v1/chat/completions");
});
test("buildUrl: xiaomi-mimo-token-plan com targetFormat claude usa o host token-plan-sgp", () => {
const executor = new DefaultExecutor("xiaomi-mimo-token-plan");
const url = executor.buildUrl("mimo-v2.5-pro", true, 0, {
apiKey: "sk-x",
providerSpecificData: { targetFormat: "claude" },
});
assert.equal(url, "https://token-plan-sgp.xiaomimimo.com/anthropic/v1/messages");
});
test("buildUrl: xiaomi-mimo-token-plan sem targetFormat mantem a URL OpenAI padrao do host token-plan-sgp", () => {
const executor = new DefaultExecutor("xiaomi-mimo-token-plan");
const url = executor.buildUrl("mimo-v2.5-pro", true, 0, {
apiKey: "sk-x",
providerSpecificData: {},
});
assert.equal(url, "https://token-plan-sgp.xiaomimimo.com/v1/chat/completions");
});
// ---------------------------------------------------------------------------
// Task 6: xiaomi-mimo-token-plan provider
// ---------------------------------------------------------------------------
test("xiaomi-mimo-token-plan aponta para o host token-plan-sgp", () => {
assert.equal(
xiaomi_mimo_token_planProvider.baseUrl,
"https://token-plan-sgp.xiaomimimo.com/v1"
);
assert.equal(xiaomi_mimo_token_planProvider.format, "openai");
assert.equal(xiaomi_mimo_token_planProvider.authHeader, "bearer");
});
test("xiaomi-mimo-token-plan declara a variante Anthropic no host token-plan", () => {
const alt = xiaomi_mimo_token_planProvider.alternateFormats?.find(
(a) => a.format === "claude"
);
assert.ok(alt, "esperava uma alternativa claude declarada");
assert.equal(alt.baseUrl, "https://token-plan-sgp.xiaomimimo.com/anthropic/v1/messages");
assert.equal(alt.authHeader, "x-api-key");
});
test("xiaomi-mimo-token-plan esta registrado e nao colide de host com o normal", () => {
assert.ok(REGISTRY["xiaomi-mimo-token-plan"], "provedor nao registrado");
assert.notEqual(
REGISTRY["xiaomi-mimo-token-plan"].baseUrl,
REGISTRY["xiaomi-mimo"].baseUrl
);
});
test("xiaomi-mimo-token-plan expoe os modelos de chat", () => {
const ids = xiaomi_mimo_token_planProvider.models.map((m) => m.id);
assert.ok(ids.includes("mimo-v2.5-pro"));
assert.ok(ids.includes("mimo-v2.5"));
});
// ---------------------------------------------------------------------------
// Task 7: monthly quota plan for the Token Plan
// ---------------------------------------------------------------------------
test("xiaomi-mimo-token-plan tem plano mensal registrado", () => {
const plan = getKnownPlan("xiaomi-mimo-token-plan");
assert.ok(plan, "esperava plano registrado para o Token Plan");
assert.equal(plan.dimensions.length, 1);
assert.equal(plan.dimensions[0].unit, "tokens");
assert.equal(plan.dimensions[0].window, "monthly");
assert.ok(plan.dimensions[0].limit > 0);
});
test("xiaomi-mimo-token-plan declara suporte a usage", () => {
assert.ok(USAGE_SUPPORTED_PROVIDERS.includes("xiaomi-mimo-token-plan"));
});