fix(provider): Add Xiaomi MiMo token plan provider (#1751)

* Add Xiaomi MiMo token plan provider

Add a descriptor-backed Xiaomi MiMo Token Plan gateway with the token-plan SGP/CN routing hosts, raw api-key OpenAI-compatible shim settings, and MiMo catalog defaults.

Wire the new preset through generated integration artifacts, provider flag handling, active route/provider resolution, provider profile env mirroring, and ProviderManager preset ordering.

Cover the new path with gateway metadata, compatibility, provider flag, provider profile, and focused ProviderManager tests. Validated with bun run build, bun run smoke, bun run check, typechecks, provider suites, integrations:check, security:pr-scan -- --base upstream/main, and doctor:runtime.

* Address Xiaomi MiMo token plan review feedback

Reset stale known OpenAI-compatible base URLs when xiaomi-mimo-token is explicitly selected so prior provider routing cannot survive the provider flag.

Use hostname-based Xiaomi MiMo base URL detection when mirroring MIMO_API_KEY from provider profiles, avoiding substring matches in unrelated URL paths or query strings.

Add focused regression coverage for stale base URL replacement and token-plan CN profile env mirroring.
This commit is contained in:
JATMN
2026-06-23 07:52:33 +08:00
committed by GitHub
parent aed42df19b
commit 091571f643
11 changed files with 280 additions and 5 deletions
+1
View File
@@ -140,6 +140,7 @@ const PRESET_ORDER = [
'Venice',
'xAI',
'Xiaomi MiMo',
'Xiaomi MiMo (Token Plan)',
'Z.AI - GLM Coding Plan',
'Custom',
] as const
+1
View File
@@ -38,6 +38,7 @@ const EXPECTED_PRESETS = [
'xai',
'venice',
'xiaomi-mimo',
'xiaomi-mimo-token',
'zai',
'bankr',
'atomic-chat',
@@ -0,0 +1,40 @@
import { describe, expect, test } from 'bun:test'
import '../index.js'
import {
getRouteDefaultBaseUrl,
getRouteDefaultModel,
resolveRouteIdFromBaseUrl,
} from '../routeMetadata.js'
describe('xiaomi-mimo-token gateway', () => {
test('default base URL is the token-plan SGP endpoint', () => {
expect(getRouteDefaultBaseUrl('xiaomi-mimo-token')).toBe(
'https://token-plan-sgp.xiaomimimo.com/v1',
)
})
test('default model is mimo-v2.5-pro', () => {
expect(getRouteDefaultModel('xiaomi-mimo-token')).toBe('mimo-v2.5-pro')
})
test('resolves token-plan SGP base URL', () => {
expect(
resolveRouteIdFromBaseUrl('https://token-plan-sgp.xiaomimimo.com/v1'),
).toBe('xiaomi-mimo-token')
})
test('resolves token-plan CN base URL', () => {
expect(
resolveRouteIdFromBaseUrl('https://token-plan-cn.xiaomimimo.com/v1'),
).toBe('xiaomi-mimo-token')
})
test('resolves token-plan chat completions path', () => {
expect(
resolveRouteIdFromBaseUrl(
'https://token-plan-sgp.xiaomimimo.com/v1/chat/completions',
),
).toBe('xiaomi-mimo-token')
})
})
@@ -0,0 +1,78 @@
import { defineCatalog, defineGateway } from '../define.js'
const catalog = defineCatalog({
source: 'static',
models: [
{
id: 'mimo-v2.5-pro',
apiName: 'mimo-v2.5-pro',
label: 'MiMo V2.5 Pro',
modelDescriptorId: 'mimo-v2.5-pro',
},
{
id: 'mimo-v2.5',
apiName: 'mimo-v2.5',
label: 'MiMo V2.5',
modelDescriptorId: 'mimo-v2.5',
},
{
id: 'mimo-v2-flash',
apiName: 'mimo-v2-flash',
label: 'MiMo V2 Flash',
modelDescriptorId: 'mimo-v2-flash',
},
],
})
export default defineGateway({
id: 'xiaomi-mimo-token',
label: 'Xiaomi MiMo (Token Plan)',
category: 'hosted',
defaultBaseUrl: 'https://token-plan-sgp.xiaomimimo.com/v1',
defaultModel: 'mimo-v2.5-pro',
setup: {
requiresAuth: true,
authMode: 'api-key',
credentialEnvVars: ['MIMO_API_KEY'],
},
transportConfig: {
kind: 'openai-compatible',
openaiShim: {
defaultAuthHeader: {
name: 'api-key',
scheme: 'raw',
},
preserveReasoningContent: true,
requireReasoningContentOnAssistantMessages: true,
maxTokensField: 'max_completion_tokens',
removeBodyFields: ['store', 'stream_options'],
supportsApiFormatSelection: false,
supportsAuthHeaders: false,
},
},
preset: {
id: 'xiaomi-mimo-token',
vendorId: 'xiaomi-mimo',
description: 'Xiaomi MiMo Token Plan subscription endpoint',
label: 'Xiaomi MiMo (Token Plan)',
name: 'Xiaomi MiMo (Token Plan)',
apiKeyEnvVars: ['MIMO_API_KEY'],
modelEnvVars: ['OPENAI_MODEL'],
badge: { text: 'Sponsor', color: 'success' },
},
validation: {
kind: 'credential-env',
routing: {
matchDefaultBaseUrl: true,
matchBaseUrlHosts: [
'token-plan-sgp.xiaomimimo.com',
'token-plan-cn.xiaomimimo.com',
],
},
credentialEnvVars: ['MIMO_API_KEY', 'OPENAI_API_KEY'],
missingCredentialMessage:
'Xiaomi MiMo Token Plan auth is required. Set MIMO_API_KEY or OPENAI_API_KEY.',
},
catalog,
usage: { supported: false },
})
@@ -37,6 +37,7 @@ import gatewayOpencode from '../gateways/opencode.js'
import gatewayOpenrouter from '../gateways/openrouter.js'
import gatewayTogether from '../gateways/together.js'
import gatewayVertex from '../gateways/vertex.js'
import gatewayXiaomiMimoToken from '../gateways/xiaomi-mimo-token.js'
import brandClaude from '../brands/claude.js'
import brandDeepseek from '../brands/deepseek.js'
import brandFireworks from '../brands/fireworks.js'
@@ -72,7 +73,7 @@ import modelXai from '../models/xai.js'
import modelXiaomiMimo from '../models/xiaomi-mimo.js'
export const VENDOR_DESCRIPTORS = [vendorAnthropic, vendorAtlasCloud, vendorBankr, vendorDeepseek, vendorFireworks, vendorGemini, vendorMinimax, vendorMoonshot, vendorNearai, vendorOpenai, vendorVenice, vendorXai, vendorXiaomiMimo, vendorZai] as const satisfies readonly VendorDescriptor[]
export const GATEWAY_DESCRIPTORS = [gatewayAtomicChat, gatewayAzureOpenai, gatewayBedrock, gatewayCustom, gatewayDashscopeCn, gatewayDashscopeIntl, gatewayGithubEnterprise, gatewayGithub, gatewayGitlawbOpengateway, gatewayGroq, gatewayHicap, gatewayKimiCode, gatewayLmstudio, gatewayMistral, gatewayNvidiaNim, gatewayOllama, gatewayOpencodeGo, gatewayOpencode, gatewayOpenrouter, gatewayTogether, gatewayVertex] as const satisfies readonly GatewayDescriptor[]
export const GATEWAY_DESCRIPTORS = [gatewayAtomicChat, gatewayAzureOpenai, gatewayBedrock, gatewayCustom, gatewayDashscopeCn, gatewayDashscopeIntl, gatewayGithubEnterprise, gatewayGithub, gatewayGitlawbOpengateway, gatewayGroq, gatewayHicap, gatewayKimiCode, gatewayLmstudio, gatewayMistral, gatewayNvidiaNim, gatewayOllama, gatewayOpencodeGo, gatewayOpencode, gatewayOpenrouter, gatewayTogether, gatewayVertex, gatewayXiaomiMimoToken] as const satisfies readonly GatewayDescriptor[]
export const ANTHROPIC_PROXY_DESCRIPTORS = [] as const satisfies readonly AnthropicProxyDescriptor[]
export const BRAND_DESCRIPTORS = [brandClaude, brandDeepseek, brandFireworks, brandGemini, brandGlm, brandGpt, brandKimi, brandLlama, brandMinimax, brandMistral, brandNearai, brandNemotron, brandOpenaiCompatibleAlias, brandQwen, brandXai, brandXiaomiMimo] as const satisfies readonly BrandDescriptor[]
export const MODEL_DESCRIPTOR_GROUPS = [modelClaude, modelDeepseek, modelFireworksMerged, modelGemini, modelGlm, modelGpt, modelKimi, modelLlama, modelMinimax, modelMistral, modelNearai, modelNemotron, modelOpenaiCompatibleAlias, modelOpencode, modelQwen, modelXai, modelXiaomiMimo] as const satisfies readonly (readonly ModelDescriptor[])[]
@@ -446,6 +447,26 @@ export const PROVIDER_PRESET_MANIFEST = [
"color": "success"
}
},
{
"preset": "xiaomi-mimo-token",
"routeKind": "gateway",
"routeId": "xiaomi-mimo-token",
"vendorId": "xiaomi-mimo",
"gatewayId": "xiaomi-mimo-token",
"description": "Xiaomi MiMo Token Plan subscription endpoint",
"label": "Xiaomi MiMo (Token Plan)",
"name": "Xiaomi MiMo (Token Plan)",
"apiKeyEnvVars": [
"MIMO_API_KEY"
],
"modelEnvVars": [
"OPENAI_MODEL"
],
"badge": {
"text": "Sponsor",
"color": "success"
}
},
{
"preset": "zai",
"routeKind": "vendor",
@@ -514,6 +535,7 @@ export const ORDERED_PROVIDER_PRESETS = [
"venice",
"xai",
"xiaomi-mimo",
"xiaomi-mimo-token",
"zai",
"custom"
] as const
+6 -1
View File
@@ -30,6 +30,10 @@ const TRANSPORT_KIND_PROVIDER_TYPE_LABELS: Partial<
const XIAOMI_MIMO_PRIMARY_HOST = 'api.xiaomimimo.com'
const XIAOMI_MIMO_STALE_DOCS_HOST = 'api.mimo-v2.com'
const XIAOMI_MIMO_TOKEN_PLAN_HOSTS = [
'token-plan-sgp.xiaomimimo.com',
'token-plan-cn.xiaomimimo.com',
]
export const XIAOMI_MIMO_PRIMARY_BASE_URL = `https://${XIAOMI_MIMO_PRIMARY_HOST}/v1`
function getValidationRoutingHosts(
@@ -247,7 +251,8 @@ export function isXiaomiMimoBaseUrl(value: string | undefined): boolean {
const hostname = new URL(trimmed).hostname.toLowerCase()
return (
hostname === XIAOMI_MIMO_PRIMARY_HOST ||
hostname === XIAOMI_MIMO_STALE_DOCS_HOST
hostname === XIAOMI_MIMO_STALE_DOCS_HOST ||
XIAOMI_MIMO_TOKEN_PLAN_HOSTS.includes(hostname)
)
} catch {
return false
+1
View File
@@ -51,6 +51,7 @@ export function getAPIProvider(): LegacyAPIProvider {
case 'minimax':
return 'minimax'
case 'xiaomi-mimo':
case 'xiaomi-mimo-token':
return 'xiaomi-mimo'
case 'xai':
return 'xai'
+35
View File
@@ -146,6 +146,7 @@ describe('VALID_PROVIDERS', () => {
expect(VALID_PROVIDERS).toContain('zai')
expect(VALID_PROVIDERS).toContain('venice')
expect(VALID_PROVIDERS).toContain('xiaomi-mimo')
expect(VALID_PROVIDERS).toContain('xiaomi-mimo-token')
})
})
@@ -536,6 +537,40 @@ describe('applyProviderFlag - xiaomi-mimo', () => {
})
})
describe('applyProviderFlag - xiaomi-mimo-token', () => {
test('sets Xiaomi MiMo Token Plan OpenAI-compatible defaults and mirrors MIMO_API_KEY', () => {
process.env.MIMO_API_KEY = 'tp-token-plan-key'
const result = applyProviderFlag('xiaomi-mimo-token', [])
expect(result.error).toBeUndefined()
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
expect(process.env.OPENAI_BASE_URL).toBe(
'https://token-plan-sgp.xiaomimimo.com/v1',
)
expect(process.env.OPENAI_MODEL).toBe('mimo-v2.5-pro')
expect(process.env.OPENAI_API_KEY).toBe('tp-token-plan-key')
})
test('replaces stale known provider base URL with the token-plan default', () => {
process.env.OPENAI_BASE_URL = 'https://openrouter.ai/api/v1'
const result = applyProviderFlag('xiaomi-mimo-token', [])
expect(result.error).toBeUndefined()
expect(process.env.CLAUDE_CODE_USE_OPENAI).toBe('1')
expect(process.env.OPENAI_BASE_URL).toBe(
'https://token-plan-sgp.xiaomimimo.com/v1',
)
})
test('sets Xiaomi MiMo Token Plan OPENAI_MODEL when --model is provided', () => {
applyProviderFlag('xiaomi-mimo-token', ['--model', 'mimo-v2-flash'])
expect(process.env.OPENAI_MODEL).toBe('mimo-v2-flash')
})
})
describe('applyProviderFlag - venice', () => {
test('sets Venice OpenAI-compatible defaults and mirrors VENICE_API_KEY', () => {
process.env.VENICE_API_KEY = 'venice-secret-key'
+13
View File
@@ -450,6 +450,19 @@ export function applyProviderFlag(
}
break
case 'xiaomi-mimo-token':
process.env.CLAUDE_CODE_USE_OPENAI = '1'
applyOpenAIBaseUrlDefault(
provider,
defaultBaseUrl ?? 'https://token-plan-sgp.xiaomimimo.com/v1',
)
process.env.OPENAI_MODEL ??= defaultModel ?? 'mimo-v2.5-pro'
if (model) process.env.OPENAI_MODEL = model
if (process.env.MIMO_API_KEY && !process.env.OPENAI_API_KEY) {
process.env.OPENAI_API_KEY = process.env.MIMO_API_KEY
}
break
case 'venice':
process.env.CLAUDE_CODE_USE_OPENAI = '1'
process.env.OPENAI_BASE_URL ??= defaultBaseUrl ?? 'https://api.venice.ai/api/v1'
+69
View File
@@ -223,6 +223,17 @@ function buildXiaomiMimoProfile(overrides: Partial<ProviderProfile> = {}): Provi
})
}
function buildXiaomiMimoTokenProfile(overrides: Partial<ProviderProfile> = {}): ProviderProfile {
return buildProfile({
provider: 'xiaomi-mimo-token',
name: 'Xiaomi MiMo Token Plan',
baseUrl: 'https://token-plan-sgp.xiaomimimo.com/v1',
model: 'mimo-v2.5-pro',
apiKey: 'tp-test-key',
...overrides,
})
}
function buildFireworksProfile(overrides: Partial<ProviderProfile> = {}): ProviderProfile {
return buildProfile({
provider: 'fireworks',
@@ -752,6 +763,48 @@ describe('applyProviderProfileToProcessEnv', () => {
expect(getFreshAPIProvider()).toBe('xiaomi-mimo')
})
test('xiaomi mimo token plan profile applies OpenAI-compatible env with MIMO_API_KEY mirror', async () => {
const { applyProviderProfileToProcessEnv } =
await importFreshProviderProfileModules()
process.env.CLAUDE_CODE_USE_GEMINI = '1'
applyProviderProfileToProcessEnv(buildXiaomiMimoTokenProfile())
const { getAPIProvider: getFreshAPIProvider } =
await importFreshProvidersModule()
expect(process.env.CLAUDE_CODE_USE_GEMINI).toBeUndefined()
expect(String(process.env.CLAUDE_CODE_USE_OPENAI)).toBe('1')
expect(process.env.OPENAI_BASE_URL).toBe(
'https://token-plan-sgp.xiaomimimo.com/v1',
)
expect(process.env.OPENAI_MODEL).toBe('mimo-v2.5-pro')
expect(process.env.OPENAI_API_KEY).toBe('tp-test-key')
expect(process.env.MIMO_API_KEY).toBe('tp-test-key')
expect(getFreshAPIProvider()).toBe('xiaomi-mimo')
})
test('xiaomi mimo token plan CN profile applies OpenAI-compatible env with MIMO_API_KEY mirror', async () => {
const { applyProviderProfileToProcessEnv } =
await importFreshProviderProfileModules()
process.env.CLAUDE_CODE_USE_GEMINI = '1'
applyProviderProfileToProcessEnv(buildXiaomiMimoTokenProfile({
baseUrl: 'https://token-plan-cn.xiaomimimo.com/v1',
}))
const { getAPIProvider: getFreshAPIProvider } =
await importFreshProvidersModule()
expect(process.env.CLAUDE_CODE_USE_GEMINI).toBeUndefined()
expect(String(process.env.CLAUDE_CODE_USE_OPENAI)).toBe('1')
expect(process.env.OPENAI_BASE_URL).toBe(
'https://token-plan-cn.xiaomimimo.com/v1',
)
expect(process.env.OPENAI_MODEL).toBe('mimo-v2.5-pro')
expect(process.env.OPENAI_API_KEY).toBe('tp-test-key')
expect(process.env.MIMO_API_KEY).toBe('tp-test-key')
expect(getFreshAPIProvider()).toBe('xiaomi-mimo')
})
test('fireworks profile applies OpenAI-compatible env with FIREWORKS_API_KEY mirror', async () => {
const { applyProviderProfileToProcessEnv } =
await importFreshProviderProfileModules()
@@ -1665,6 +1718,22 @@ describe('getProviderPresetDefaults', () => {
expect(defaults.requiresApiKey).toBe(true)
})
test('xiaomi mimo token plan preset defaults to the token-plan SGP endpoint', async () => {
const { getProviderPresetDefaults } = await importFreshProviderProfileModules()
process.env.MIMO_API_KEY = 'tp-live-key'
const defaults = getProviderPresetDefaults('xiaomi-mimo-token')
expect(defaults.provider).toBe('xiaomi-mimo-token')
expect(defaults.name).toBe('Xiaomi MiMo (Token Plan)')
expect(defaults.baseUrl).toBe(
'https://token-plan-sgp.xiaomimimo.com/v1',
)
expect(defaults.model).toBe('mimo-v2.5-pro')
expect(defaults.apiKey).toBe('tp-live-key')
expect(defaults.requiresApiKey).toBe(true)
})
test('xai preset ignores stale generic OpenAI model when creating defaults', async () => {
const { getProviderPresetDefaults } = await importFreshProviderProfileModules()
process.env.OPENAI_MODEL = 'gpt-5.4'
+13 -3
View File
@@ -44,7 +44,13 @@ import {
type ResolvedProfileRoute,
type ProviderPreset,
} from '../integrations/index.js'
import { isFireworksBaseUrl, isNearaiBaseUrl, isXaiBaseUrl, resolveEnvOnlyProviderRouteId } from '../integrations/routeMetadata.js'
import {
isFireworksBaseUrl,
isNearaiBaseUrl,
isXaiBaseUrl,
isXiaomiMimoBaseUrl,
resolveEnvOnlyProviderRouteId,
} from '../integrations/routeMetadata.js'
import { logForDebugging } from './debug.js'
import {
sanitizeProfileCustomHeaders,
@@ -739,7 +745,7 @@ export function applyProviderProfileToProcessEnv(profile: ProviderProfile): void
const supportsApiFormat = routeSupportsApiFormatSelection(capabilityRouteId)
const supportsAuthHeaders = routeSupportsAuthHeaders(capabilityRouteId)
const normalizedProfileBaseUrl =
route.routeId === 'xiaomi-mimo'
route.routeId === 'xiaomi-mimo' || route.routeId === 'xiaomi-mimo-token'
? normalizeXiaomiMimoBaseUrl(profile.baseUrl) ?? profile.baseUrl
: profile.baseUrl
const openAIProfileEnv: ProfileEnv = {
@@ -782,7 +788,11 @@ export function applyProviderProfileToProcessEnv(profile: ProviderProfile): void
if (route.routeId === 'venice' || profile.baseUrl.toLowerCase().includes('api.venice.ai')) {
openAIProfileEnv.VENICE_API_KEY = profile.apiKey
}
if (route.routeId === 'xiaomi-mimo' || profile.baseUrl.toLowerCase().includes('api.xiaomimimo.com') || profile.baseUrl.toLowerCase().includes('api.mimo-v2.com')) {
if (
route.routeId === 'xiaomi-mimo' ||
route.routeId === 'xiaomi-mimo-token' ||
isXiaomiMimoBaseUrl(profile.baseUrl)
) {
openAIProfileEnv.MIMO_API_KEY = profile.apiKey
}
if (route.routeId === 'atlas-cloud' || profile.baseUrl.toLowerCase().includes('atlascloud')) {