Files
config-center/__tests__/validate.test.mjs
Yige b64ee77342 feat(compute): update current model catalog (#106)
* feat(compute): update current model catalog

* feat(compute): add MiMo V2.6 model profiles
2026-09-25 23:33:23 +08:00

1433 lines
62 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* config-center frozen schema 校验测试
*
* 关键场景:
* - 现有所有数据文件必须通过 frozen schema 校验
* - PR #1 引发死锁的反例(defaultTemperature: null)必须被拒绝
* - 防御未知字段(additionalProperties: false)保护老客户端
*
* 运行:
* npm test
* node --test __tests__/
*/
import { describe, it } from 'node:test'
import { strict as assert } from 'node:assert'
import { existsSync, readFileSync, readdirSync } from 'node:fs'
import { dirname, join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { Ajv } from 'ajv'
import addFormats from 'ajv-formats'
import { validateFile, loadSchemas } from '../scripts/validate.mjs'
const validators = loadSchemas()
const __dirname = dirname(fileURLToPath(import.meta.url))
const ROOT = resolve(__dirname, '..')
function loadSchema(name) {
const p = join(ROOT, 'schemas', `${name}.schema.json`)
return JSON.parse(readFileSync(p, 'utf8'))
}
function compile(name) {
const ajv = new Ajv({ allErrors: true, strict: false })
addFormats(ajv)
return ajv.compile(loadSchema(name))
}
// ==================== 真实数据 happy path ====================
describe('真实数据全量校验', () => {
it('所有 compute/providers/*.json 应通过 provider schema', () => {
const dir = join(ROOT, 'compute', 'providers')
const failures = []
for (const file of readdirSync(dir)) {
if (!file.endsWith('.json') || file === '_index.json') continue
const result = validateFile(join(dir, file), validators)
if (!result.ok) failures.push({ file, errors: result.errors })
}
assert.equal(
failures.length,
0,
`${failures.length} provider 文件校验失败:\n${JSON.stringify(failures, null, 2)}`,
)
})
it('所有 compute/coding-plans/*.json 应通过 provider schema', () => {
const dir = join(ROOT, 'compute', 'coding-plans')
const failures = []
for (const file of readdirSync(dir)) {
if (!file.endsWith('.json') || file === '_index.json') continue
const result = validateFile(join(dir, file), validators)
if (!result.ok) failures.push({ file, errors: result.errors })
}
assert.equal(
failures.length,
0,
`${failures.length} coding-plan 文件校验失败:\n${JSON.stringify(failures, null, 2)}`,
)
})
it('manifest.json 应通过 manifest schema', () => {
const result = validateFile(join(ROOT, 'manifest.json'), validators)
assert.equal(result.ok, true, JSON.stringify(result.errors, null, 2))
})
it('compute/pricing.json 应通过 pricing schema', () => {
const result = validateFile(join(ROOT, 'compute', 'pricing.json'), validators)
assert.equal(result.ok, true, JSON.stringify(result.errors, null, 2))
})
it('compute/service-map.json 应通过 service-map schema', () => {
const result = validateFile(join(ROOT, 'compute', 'service-map.json'), validators)
assert.equal(result.ok, true, JSON.stringify(result.errors, null, 2))
})
it('model-specs 是智能路由的唯一规格主数据', () => {
const indexPath = join(ROOT, 'compute', 'model-specs', '_index.json')
const result = validateFile(indexPath, validators)
assert.equal(result.ok, true, JSON.stringify(result.errors, null, 2))
const index = JSON.parse(readFileSync(indexPath, 'utf8'))
assert.deepEqual(index.routingTiers.map((tier) => tier.id), [
'flagship',
'balanced',
'lightweight',
])
assert.equal(existsSync(join(ROOT, 'compute', 'smart-routing')), false)
const routed = []
for (const name of index.order) {
const file = JSON.parse(readFileSync(join(ROOT, 'compute', 'model-specs', `${name}.json`), 'utf8'))
for (const spec of file.specs) {
if (spec.routing) routed.push(spec)
}
}
assert.equal(routed.length, 58)
assert.equal(routed.every((spec) => spec.routing.reasoning.supportedModes.includes(spec.routing.reasoning.defaultMode)), true)
assert.equal(routed.every((spec) => Array.isArray(spec.spec.capabilities)), true)
})
it('智能路由三档必须完整唯一,且每档从自身开始回退', () => {
const indexPath = join(ROOT, 'compute', 'model-specs', '_index.json')
const index = JSON.parse(readFileSync(indexPath, 'utf8'))
const validate = compile('model-specs-index')
const duplicateTier = structuredClone(index)
duplicateTier.routingTiers[1] = structuredClone(duplicateTier.routingTiers[0])
assert.equal(validate(duplicateTier), false)
const wrongFirstFallback = structuredClone(index)
wrongFirstFallback.routingTiers[0].fallbackOrder = ['balanced', 'flagship', 'lightweight']
assert.equal(validate(wrongFirstFallback), false)
})
it('Provider 与 coding plan 的 _index.json 应通过 providers-index schema', () => {
const r1 = validateFile(join(ROOT, 'compute', 'providers', '_index.json'), validators)
const r2 = validateFile(join(ROOT, 'compute', 'coding-plans', '_index.json'), validators)
assert.equal(r1.ok, true, JSON.stringify(r1.errors, null, 2))
assert.equal(r2.ok, true, JSON.stringify(r2.errors, null, 2))
})
it('runtimes/recommended.json 应通过 runtime-recommended schema', () => {
const result = validateFile(join(ROOT, 'runtimes', 'recommended.json'), validators)
assert.equal(result.ok, true, JSON.stringify(result.errors, null, 2))
})
it('runtimes/versions-fallback.json 应通过 runtime-versions-fallback schema', () => {
const result = validateFile(join(ROOT, 'runtimes', 'versions-fallback.json'), validators)
assert.equal(result.ok, true, JSON.stringify(result.errors, null, 2))
})
it('WebSearch 服务端能力仅标记官方直连 Anthropic/OpenAI 模型', () => {
const anthropic = JSON.parse(readFileSync(join(ROOT, 'compute', 'providers', 'anthropic.json'), 'utf8'))
const openai = JSON.parse(readFileSync(join(ROOT, 'compute', 'providers', 'openai.json'), 'utf8'))
const openaiCodex = JSON.parse(readFileSync(join(ROOT, 'compute', 'providers', 'openai-codex.json'), 'utf8'))
const enabled = (provider) => provider.models
.filter((model) => model.extra?.serverSideWebSearch?.enabled === true)
.map((model) => model.modelName)
assert.deepEqual(enabled(anthropic), [
'claude-opus-5-5',
'claude-fable-5-1',
'claude-opus-5',
'claude-sonnet-5',
])
assert.deepEqual(enabled(openai), [
'gpt-6-sol',
'gpt-6-luna',
'gpt-6-astra',
'gpt-5.5',
'gpt-5.5-pro',
'gpt-5.4',
'gpt-5.4-pro',
'gpt-5.4-mini',
'gpt-5.4-nano',
'gpt-5',
'gpt-4.1',
'gpt-4.1-mini',
'o4-mini',
])
assert.deepEqual(enabled(openaiCodex), [])
for (const model of anthropic.models.filter((item) => enabled(anthropic).includes(item.modelName))) {
assert.equal(model.extra.serverSideWebSearch.dialect, 'anthropic-messages')
assert.equal(model.extra.serverSideWebSearch.toolType, 'web_search_20260318')
assert.equal(model.extra.serverSideWebSearch.searchRequestPriceUsd, 0.01)
assert.equal('maxUses' in model.extra.serverSideWebSearch, false)
}
for (const model of openai.models.filter((item) => enabled(openai).includes(item.modelName))) {
assert.equal(model.extra.serverSideWebSearch.dialect, 'openai-responses')
assert.equal(model.extra.serverSideWebSearch.toolType, 'web_search')
assert.equal(model.extra.serverSideWebSearch.searchRequestPriceUsd, 0.01)
assert.equal('maxUses' in model.extra.serverSideWebSearch, false)
}
})
it('WebSearch API providers 使用显式密钥声明且默认关闭', () => {
const brave = JSON.parse(readFileSync(join(ROOT, 'api-providers', 'web-search', 'brave.json'), 'utf8'))
const doubao = JSON.parse(readFileSync(join(ROOT, 'api-providers', 'web-search', 'doubao-search-global.json'), 'utf8'))
const index = JSON.parse(readFileSync(join(ROOT, 'api-providers', 'web-search', '_index.json'), 'utf8'))
assert.equal(brave.enabled, false)
assert.equal(brave.endpoint, 'https://api.search.brave.com/res/v1/web/search')
assert.deepEqual(brave.auth, {
type: 'header',
headerName: 'X-Subscription-Token',
apiKeyRef: 'brave',
})
assert.equal(doubao.enabled, false)
assert.equal(doubao.priority, 5)
assert.equal(doubao.endpoint, 'https://open.feedcoopapi.com/search_api/global_search')
assert.deepEqual(doubao.auth, {
type: 'bearer',
apiKeyRef: 'doubao-search-global',
})
assert.deepEqual(doubao.request.bodyTemplate, {
Query: '${query}',
SearchType: 'web',
DocCount: '${maxResults}',
MaxSnippetLength: 500,
})
assert.equal(doubao.response.resultsPath, 'Result.Documents')
assert.deepEqual(doubao.response.item, {
titlePath: 'Title',
urlPath: 'Url',
snippetPath: 'Snippet.0.Text',
pageAgePath: 'DocumentInfo.PublishTime',
})
assert.deepEqual(doubao.response.successCondition, {
path: 'Result.ErrorCode',
equals: 0,
})
assert.deepEqual(index.order, ['tavily', 'brave', 'serper', 'doubao-search-global'])
assert.equal(
validateFile(join(ROOT, 'api-providers', 'web-search', 'brave.json'), validators).ok,
true,
)
assert.equal(
validateFile(join(ROOT, 'api-providers', 'web-search', 'doubao-search-global.json'), validators).ok,
true,
)
})
it('DeepSeek V4 Pro/Flash 应与官方的 1M/384K reasoning profile 一致', () => {
const provider = JSON.parse(readFileSync(join(ROOT, 'compute', 'providers', 'deepseek.json'), 'utf8'))
const specFile = JSON.parse(readFileSync(join(ROOT, 'compute', 'model-specs', 'deepseek.json'), 'utf8'))
const modelIds = ['deepseek-v4-pro', 'deepseek-v4-flash']
for (const modelId of modelIds) {
const model = provider.models.find((item) => item.modelName === modelId)
assert.ok(model, `provider 缺少 ${modelId}`)
assert.equal(model.contextWindow, 1000000)
assert.equal(model.maxOutputTokens, 384000)
assert.ok(model.capabilities.includes('reasoning'))
assert.ok(model.capabilities.includes('deep_thinking'))
assert.ok(model.serviceType.includes('reasoning'))
assert.equal(model.extra.supportsThinking, true)
assert.equal(model.extra.thinkingDefault, true)
assert.deepEqual(model.extra.reasoningEffort, ['high', 'max'])
const specs = specFile.specs.filter((item) => item.id === modelId)
assert.equal(specs.length, 1, `model-specs 中 ${modelId} 应且仅应有一条规格`)
for (const { spec } of specs) {
assert.equal(spec.contextWindow, 1000000)
assert.equal(spec.maxOutputTokens, 384000)
assert.equal(spec.supportsReasoning, true)
assert.ok(spec.capabilities.includes('reasoning'))
assert.ok(spec.capabilities.includes('deep_thinking'))
assert.ok(spec.serviceType.includes('reasoning'))
assert.equal(spec.extra.supportsThinking, true)
assert.equal(spec.extra.thinkingDefault, true)
assert.deepEqual(spec.extra.reasoningEffort, ['high', 'max'])
}
}
})
it('DeepSeek V4 Flash Vision Exp 应提供独立的视觉规格', () => {
const provider = JSON.parse(readFileSync(join(ROOT, 'compute', 'providers', 'deepseek.json'), 'utf8'))
const specFile = JSON.parse(readFileSync(join(ROOT, 'compute', 'model-specs', 'deepseek.json'), 'utf8'))
const modelId = 'deepseek-v4-flash-vision-exp'
const model = provider.models.find((item) => item.modelName === modelId)
assert.ok(provider.services.includes('vision'))
assert.ok(model, `provider 缺少 ${modelId}`)
assert.equal(model.contextWindow, 1000000)
assert.equal(model.maxOutputTokens, 384000)
assert.ok(model.capabilities.includes('vision'))
assert.ok(model.serviceType.includes('vision'))
assert.equal(model.extra.supportsThinking, true)
assert.equal(model.extra.thinkingDefault, true)
const specs = specFile.specs.filter((item) => item.id === modelId)
assert.equal(specs.length, 1, `model-specs 中 ${modelId} 应且仅应有一条规格`)
assert.deepEqual(specs[0].match.exact, [modelId])
assert.equal('patterns' in specs[0].match, false)
assert.equal(specs[0].family, modelId)
assert.equal(specs[0].spec.contextWindow, 1000000)
assert.equal(specs[0].spec.maxOutputTokens, 384000)
assert.equal(specs[0].spec.supportsReasoning, true)
assert.ok(specs[0].spec.capabilities.includes('vision'))
assert.ok(specs[0].spec.serviceType.includes('vision'))
const flash = specFile.specs.find((item) => item.id === 'deepseek-v4-flash')
assert.deepEqual(flash.match.exact, ['deepseek-v4-flash'])
assert.equal('patterns' in flash.match, false)
})
it('DeepSeek V4.1 Flash 应使用官方模型 ID、视觉能力和分时价格', () => {
const provider = JSON.parse(readFileSync(join(ROOT, 'compute', 'providers', 'deepseek.json'), 'utf8'))
const specFile = JSON.parse(readFileSync(join(ROOT, 'compute', 'model-specs', 'deepseek.json'), 'utf8'))
const serviceMap = JSON.parse(readFileSync(join(ROOT, 'compute', 'service-map.json'), 'utf8'))
const modelId = 'deepseek-flash'
const aliasId = 'deepseek-v4.1-flash'
const model = provider.models.find((item) => item.modelName === modelId)
const modelSpec = specFile.specs.find((item) => item.id === modelId)
assert.equal(serviceMap.reasoning.modelName, modelId)
assert.ok(model, `provider 缺少 ${modelId}`)
assert.deepEqual(model.serviceType, ['chat', 'reasoning', 'vision'])
assert.equal(model.contextWindow, 1000000)
assert.equal(model.maxOutputTokens, 384000)
assert.ok(model.capabilities.includes('vision'))
assert.equal(model.inputPrice, 1)
assert.equal(model.outputPrice, 4)
assert.equal(model.extra.cacheHitPrice, 0.02)
assert.equal(model.extra.concurrencyLimit, 2500)
assert.deepEqual(model.extra.reasoning, {
supportedEfforts: ['high', 'max'],
defaultEffort: 'high',
})
assert.deepEqual(model.extra.pricingTiers, [
{ condition: 'idle', inputPrice: 1, outputPrice: 4, cacheHitPrice: 0.02 },
{ condition: 'peak', inputPrice: 2, outputPrice: 8, cacheHitPrice: 0.04 },
])
assert.ok(modelSpec, `model-specs 缺少 ${modelId}`)
assert.deepEqual(modelSpec.match.exact, [modelId, aliasId])
assert.equal(
specFile.specs.filter((item) => item.match?.exact?.includes(aliasId)).length,
1,
)
assert.ok(modelSpec.match.exact.includes(aliasId))
assert.equal(modelSpec.spec.contextWindow, 1000000)
assert.equal(modelSpec.spec.maxOutputTokens, 384000)
assert.ok(modelSpec.spec.capabilities.includes('vision'))
assert.deepEqual(modelSpec.spec.serviceType, ['chat', 'reasoning', 'vision'])
assert.equal(modelSpec.spec.supportsReasoning, true)
assert.deepEqual(modelSpec.routing.reasoning.supportedModes, ['auto', 'high', 'max'])
})
it('MiMo V2.5 ASR 应有独立精确规格,避免回落到 MiMo V2.5 family', () => {
const specFile = JSON.parse(readFileSync(join(ROOT, 'compute', 'model-specs', 'xiaomi.json'), 'utf8'))
const specs = specFile.specs.filter((item) => item.id === 'mimo-v2.5-asr')
assert.equal(specs.length, 1, 'mimo-v2.5-asr 应且仅应有一条规格')
assert.deepEqual(specs[0].match.exact, ['mimo-v2.5-asr'])
assert.equal(specs[0].spec.contextWindow, 8192)
assert.equal(specs[0].spec.maxOutputTokens, 2048)
assert.deepEqual(specs[0].spec.serviceType, ['asr'])
assert.ok(specs[0].spec.capabilities.includes('asr'))
assert.deepEqual(specs[0].spec.extra.modelOrigin, {
vendor: 'xiaomi',
canonicalModelId: 'mimo-v2.5-asr',
family: 'mimo-asr',
})
assert.deepEqual(specs[0].spec.extra.speech, {
protocol: 'xiaomi-mimo-chat-asr',
protocolVersion: '1',
modes: ['batch'],
inputFormats: ['wav', 'mp3'],
languageHints: true,
})
})
it('MiMo V2.5 TTS 应声明稳定协议与官方音色', () => {
const specFile = JSON.parse(readFileSync(join(ROOT, 'compute', 'model-specs', 'xiaomi.json'), 'utf8'))
const provider = JSON.parse(readFileSync(join(ROOT, 'compute', 'providers', 'xiaomi.json'), 'utf8'))
const tts = specFile.specs.find((item) => item.id === 'mimo-v2.5-tts')
const providerTts = provider.models.find((item) => item.modelName === 'mimo-v2.5-tts')
const providerAsr = provider.models.find((item) => item.modelName === 'mimo-v2.5-asr')
assert.ok(tts, 'model-specs 中缺少 mimo-v2.5-tts')
assert.ok(providerTts, 'provider 中缺少 mimo-v2.5-tts')
assert.ok(providerAsr, 'provider 中缺少 mimo-v2.5-asr')
assert.ok(provider.services.includes('asr'))
assert.equal(tts.spec.contextWindow, 8192)
assert.equal(tts.spec.maxOutputTokens, 8192)
assert.deepEqual(tts.spec.extra.modelOrigin, {
vendor: 'xiaomi',
canonicalModelId: 'mimo-v2.5-tts',
family: 'mimo-tts',
})
assert.equal(tts.spec.extra.speech.protocol, 'xiaomi-mimo-chat-tts')
assert.equal(tts.spec.extra.speech.protocolVersion, '1')
assert.deepEqual(tts.spec.extra.speech.modes, ['batch', 'streaming'])
assert.deepEqual(tts.spec.extra.speech.outputFormats, ['wav', 'mp3', 'pcm16le'])
assert.deepEqual(tts.spec.extra.speech.sampleRates, [24000])
assert.equal(tts.spec.extra.speech.defaultVoice, 'mimo_default')
assert.deepEqual(
tts.spec.extra.speech.voices.map((voice) => voice.id),
['mimo_default', '冰糖', '茉莉', '苏打', '白桦', 'Mia', 'Chloe', 'Milo', 'Dean'],
)
assert.deepEqual(
providerTts.extra.voices.map((voice) => voice.id),
['mimo_default', '冰糖', '茉莉', '苏打', '白桦', 'Mia', 'Chloe', 'Milo', 'Dean'],
)
assert.deepEqual(providerTts.extra.audioFormats, ['wav', 'mp3', 'pcm16le'])
assert.equal(providerTts.extra.sampleRate, 24000)
})
it('OpenRouter 2026-07-21 水位线前的缺失模型应使用当前精确规格', () => {
const openai = JSON.parse(readFileSync(join(ROOT, 'compute', 'model-specs', 'openai.json'), 'utf8'))
const google = JSON.parse(readFileSync(join(ROOT, 'compute', 'model-specs', 'google.json'), 'utf8'))
const tencent = JSON.parse(readFileSync(join(ROOT, 'compute', 'model-specs', 'tencent.json'), 'utf8'))
const xai = JSON.parse(readFileSync(join(ROOT, 'compute', 'model-specs', 'xai.json'), 'utf8'))
for (const id of ['gpt-5.6-sol-pro', 'gpt-5.6-terra-pro', 'gpt-5.6-luna-pro']) {
const modelSpec = openai.specs.find((item) => item.id === id)
assert.ok(modelSpec, `model-specs/openai.json 缺少 ${id}`)
assert.equal(modelSpec.family, id)
assert.deepEqual(modelSpec.match.exact, [id])
assert.equal('patterns' in modelSpec.match, false)
assert.equal(modelSpec.spec.contextWindow, 1050000)
assert.equal(modelSpec.spec.maxOutputTokens, 128000)
assert.equal(modelSpec.spec.supportsReasoning, true)
}
const gemini = google.specs.find((item) => item.id === 'gemini-3.1-flash-lite-image')
assert.ok(gemini, 'model-specs/google.json 缺少 gemini-3.1-flash-lite-image')
assert.deepEqual(gemini.match.exact, ['gemini-3.1-flash-lite-image'])
assert.equal(gemini.spec.contextWindow, 65536)
assert.equal(gemini.spec.maxOutputTokens, 4096)
assert.ok(gemini.spec.capabilities.includes('image_generation'))
assert.ok(gemini.spec.capabilities.includes('image_editing'))
const hy3 = tencent.specs.find((item) => item.id === 'hy3')
assert.ok(hy3, 'model-specs/tencent.json 缺少 hy3')
assert.deepEqual(hy3.match.exact, ['hy3'])
assert.equal(hy3.spec.contextWindow, 262144)
assert.equal(hy3.spec.maxOutputTokens, 128000)
const grok = xai.specs.find((item) => item.id === 'grok-4.5')
assert.ok(grok, 'model-specs/xai.json 缺少 grok-4.5')
assert.deepEqual(grok.match.exact, ['grok-4.5'])
assert.equal(grok.spec.contextWindow, 500000)
assert.equal(grok.spec.maxOutputTokens, 450000)
assert.equal(grok.spec.extra.thinkingOnly, true)
const watermark = readFileSync(join(ROOT, '.sync-watermark'), 'utf8')
assert.match(watermark, /# 人读: 2026-07-21T03:37:38Z/)
assert.equal(Number(watermark.trim().split(/\s+/).at(-1)), 1784554658)
})
it('Ox Alpha 应提供与 OpenRouter 一致的多模态推理规格', () => {
const provider = JSON.parse(readFileSync(join(ROOT, 'compute', 'providers', 'openrouter.json'), 'utf8'))
const specFile = JSON.parse(readFileSync(join(ROOT, 'compute', 'model-specs', 'stealth.json'), 'utf8'))
const model = provider.models.find((item) => item.modelName === 'stealth/ox-alpha')
const modelSpec = specFile.specs.find((item) => item.id === 'ox-alpha')
assert.ok(model, 'OpenRouter provider 缺少 stealth/ox-alpha')
assert.ok(modelSpec, 'model-specs 缺少 ox-alpha')
for (const item of [model, modelSpec.spec]) {
assert.equal(item.contextWindow, 1048576)
assert.equal(item.maxOutputTokens, 131072)
assert.equal(item.defaultTemperature, 1)
assert.equal(item.defaultTopP, 0.95)
assert.ok(item.capabilities.includes('reasoning'))
assert.ok(item.capabilities.includes('vision'))
assert.ok(item.capabilities.includes('video_understanding'))
}
assert.deepEqual(model.extra.reasoning.supportedEfforts, ['low', 'high', 'max'])
assert.equal(model.extra.reasoning.defaultEffort, 'max')
assert.equal(model.extra.thinking.disableSupported, false)
assert.equal(model.inputPrice, 0)
assert.equal(model.outputPrice, 0)
assert.deepEqual(modelSpec.match.exact, ['ox-alpha', 'stealth/ox-alpha'])
assert.equal(modelSpec.spec.supportsReasoning, true)
assert.equal(modelSpec.spec.releasedAt, '2026-08-21')
})
it('MiMo V2.5 仅非 Pro 型号应声明多模态能力', () => {
const provider = JSON.parse(readFileSync(join(ROOT, 'compute', 'providers', 'xiaomi.json'), 'utf8'))
const specFile = JSON.parse(readFileSync(join(ROOT, 'compute', 'model-specs', 'xiaomi.json'), 'utf8'))
const specFor = (modelId) => specFile.specs.find((item) => item.id === modelId)
const mimoV25 = specFor('mimo-v2.5')
assert.ok(mimoV25, 'model-specs 缺少 mimo-v2.5')
assert.ok(mimoV25.spec.capabilities.includes('vision'))
assert.ok(mimoV25.spec.serviceType.includes('vision'))
for (const modelId of ['mimo-v2.5-pro', 'mimo-v2-pro']) {
const modelSpec = specFor(modelId)
assert.ok(modelSpec, `model-specs 缺少 ${modelId}`)
assert.equal(modelSpec.spec.capabilities.includes('vision'), false)
assert.equal(modelSpec.spec.serviceType.includes('vision'), false)
}
const pro = provider.models.find((item) => item.modelName === 'mimo-v2.5-pro')
assert.ok(pro, 'provider 缺少 mimo-v2.5-pro')
assert.equal(pro.capabilities.includes('vision'), false)
assert.equal(pro.serviceType.includes('vision'), false)
})
it('MiMo-X 内测模型应声明 1M 上下文与图片识别能力', () => {
const provider = JSON.parse(readFileSync(join(ROOT, 'compute', 'providers', 'xiaomi.json'), 'utf8'))
const specFile = JSON.parse(readFileSync(join(ROOT, 'compute', 'model-specs', 'xiaomi.json'), 'utf8'))
const modelIds = ['mimo-x-flash-preview', 'mimo-x-pro-preview']
assert.ok(provider.services.includes('vision'))
for (const modelId of modelIds) {
const model = provider.models.find((item) => item.modelName === modelId)
const modelSpec = specFile.specs.find((item) => item.id === modelId)
assert.ok(model, `provider 缺少 ${modelId}`)
assert.ok(modelSpec, `model-specs 缺少 ${modelId}`)
assert.deepEqual(modelSpec.match.exact, [modelId])
assert.equal(model.contextWindow, 1000000)
assert.equal(modelSpec.spec.contextWindow, 1000000)
assert.ok(model.capabilities.includes('vision'))
assert.ok(model.serviceType.includes('vision'))
assert.ok(modelSpec.spec.capabilities.includes('vision'))
assert.ok(modelSpec.spec.serviceType.includes('vision'))
}
})
it('MiMo V2.6 三款官方 API 模型应保持全模态规格与接入差异', () => {
const provider = JSON.parse(readFileSync(join(ROOT, 'compute', 'providers', 'xiaomi.json'), 'utf8'))
const specFile = JSON.parse(readFileSync(join(ROOT, 'compute', 'model-specs', 'xiaomi.json'), 'utf8'))
const cases = [
['mimo-v2.6-pro', 3, 6, 0.025, 'flagship'],
['mimo-v2.6-flash', 1, 2, 0.02, 'balanced'],
['mimo-v2.6-pro-ultraspeed', 30, 60, 0.25, null],
]
for (const [id, inputPrice, outputPrice, cachedInputPrice, tier] of cases) {
const models = provider.models.filter((item) => item.modelName === id)
const specs = specFile.specs.filter((item) => item.id === id)
assert.equal(models.length, 1, `Provider 缺少或重复 ${id}`)
assert.equal(specs.length, 1, `model-specs 缺少或重复 ${id}`)
const model = models[0]
const modelSpec = specs[0]
assert.deepEqual(modelSpec.match.exact, [id])
assert.equal(model.contextWindow, 1000000)
assert.equal(model.maxOutputTokens, 131072)
assert.equal(modelSpec.spec.contextWindow, model.contextWindow)
assert.equal(modelSpec.spec.maxOutputTokens, model.maxOutputTokens)
assert.equal(model.inputPrice, inputPrice)
assert.equal(model.outputPrice, outputPrice)
assert.equal(model.extra.cachedInputPrice, cachedInputPrice)
for (const capability of ['vision', 'audio_understanding', 'video_understanding', 'tool_use']) {
assert.ok(model.capabilities.includes(capability), `${id} 缺少 ${capability}`)
assert.ok(modelSpec.spec.capabilities.includes(capability), `${id} 规格缺少 ${capability}`)
}
assert.equal(modelSpec.spec.extra.thinkingDefault, true)
assert.equal(modelSpec.routing?.tier ?? null, tier)
}
const previous = provider.models.find((item) => item.modelName === 'mimo-v2.5-pro')
assert.ok(previous, '尚未到官方下线日,应保留 mimo-v2.5-pro')
assert.equal(previous.maxOutputTokens, 131072)
})
it('Token Plan 应使用 Qwen3.8 正式版 ID 并淘汰 Preview 预置', () => {
const tokenPlan = JSON.parse(readFileSync(join(ROOT, 'compute', 'coding-plans', 'dashscope-token-plan.json'), 'utf8'))
const apiProvider = JSON.parse(readFileSync(join(ROOT, 'compute', 'providers', 'dashscope.json'), 'utf8'))
const specFile = JSON.parse(readFileSync(join(ROOT, 'compute', 'model-specs', 'qwen.json'), 'utf8'))
assert.equal(tokenPlan.models.some((item) => item.modelName === 'qwen3.8-max-preview'), false)
assert.equal(apiProvider.models.some((item) => item.modelName === 'qwen3.8-max-preview'), false)
assert.ok(tokenPlan.tombstones.includes('qwen3.8-max-preview'))
assert.ok(apiProvider.tombstones.includes('qwen3.8-max-preview'))
for (const modelId of ['qwen3.8-max', 'qwen3.8-flash']) {
const model = tokenPlan.models.find((item) => item.modelName === modelId)
const modelSpec = specFile.specs.find((item) => item.id === modelId)
assert.ok(model, `Token Plan 缺少 ${modelId}`)
assert.ok(modelSpec, `model-specs 缺少 ${modelId}`)
assert.equal(model.contextWindow, 1000000)
assert.equal(model.maxOutputTokens, 131072)
assert.ok(model.capabilities.includes('vision'))
assert.deepEqual(model.extra.reasoning.supportedEfforts, ['low', 'medium', 'xhigh'])
assert.equal(model.extra.reasoning.defaultEffort, 'xhigh')
assert.equal(model.extra.thinkingOnly, undefined)
assert.equal(model.extra.preserveThinkingDefault, true)
assert.equal(modelSpec.spec.maxOutputTokens, 131072)
const apiModel = apiProvider.models.find((item) => item.modelName === modelId)
assert.deepEqual(apiModel.extra.reasoning.supportedEfforts, ['low', 'medium', 'xhigh'])
assert.equal(apiModel.extra.preserveThinkingDefault, true)
}
})
it('新增主流模型应在官方 Provider 与共享规格中保持 ID、窗口和推理配置一致', () => {
const cases = [
['openai', 'openai', 'gpt-6-sol', 1050000, 128000, 2, 10, 'medium'],
['openai', 'openai', 'gpt-6-luna', 1050000, 128000, 0.1, 0.5, 'medium'],
['anthropic', 'anthropic', 'claude-opus-5-5', 1000000, 128000, 4, 20, 'medium'],
['google', 'google', 'gemini-3.8-flash', 1048576, 65536, 0.75, 3.75, null],
['google', 'google', 'gemini-3.7-flash', 1048576, 65536, 0.75, 3.75, null],
['google', 'google', 'gemini-3.5-flash-lite', 1048576, 65536, 0.3, 2.5, null],
['dashscope', 'qwen', 'qwen3.8-max', 1000000, 131072, 12, 36, null],
['dashscope', 'qwen', 'qwen3.8-flash', 1000000, 131072, 0.8, 2.7, null],
['xai', 'xai', 'grok-4.7', 500000, undefined, 2, 6, 'high'],
]
for (const [providerName, specName, id, contextWindow, maxOutputTokens, inputPrice, outputPrice, defaultEffort] of cases) {
const provider = JSON.parse(readFileSync(join(ROOT, 'compute', 'providers', `${providerName}.json`), 'utf8'))
const specFile = JSON.parse(readFileSync(join(ROOT, 'compute', 'model-specs', `${specName}.json`), 'utf8'))
const models = provider.models.filter((item) => item.modelName === id)
const specs = specFile.specs.filter((item) => item.id === id)
assert.equal(models.length, 1, `${providerName} 缺少或重复 ${id}`)
assert.equal(specs.length, 1, `${specName} 缺少或重复 ${id}`)
assert.equal(models[0].contextWindow, contextWindow)
assert.equal(specs[0].spec.contextWindow, contextWindow)
assert.equal(models[0].maxOutputTokens, maxOutputTokens)
assert.equal(specs[0].spec.maxOutputTokens, maxOutputTokens)
assert.equal(models[0].inputPrice, inputPrice)
assert.equal(models[0].outputPrice, outputPrice)
assert.equal(specs[0].routing.reasoning.supportedModes.includes(specs[0].routing.reasoning.defaultMode), true)
if (defaultEffort) assert.equal(models[0].extra.reasoning.defaultEffort, defaultEffort)
}
const anthropic = JSON.parse(readFileSync(join(ROOT, 'compute', 'providers', 'anthropic.json'), 'utf8'))
const opus = anthropic.models.find((item) => item.modelName === 'claude-opus-5-5')
assert.equal(opus.extra.adaptiveThinking, true)
assert.equal(opus.extra.thinkingOnly, true)
assert.equal(opus.extra.forcedToolChoiceUnsupported, true)
assert.equal(opus.extra.cachePricing.read, 0.2)
const xai = JSON.parse(readFileSync(join(ROOT, 'compute', 'providers', 'xai.json'), 'utf8'))
assert.deepEqual(xai.models.find((item) => item.modelName === 'grok-4.7').extra.reasoning.supportedEfforts, ['low', 'medium', 'high', 'xhigh'])
const codex = JSON.parse(readFileSync(join(ROOT, 'compute', 'providers', 'openai-codex.json'), 'utf8'))
for (const id of ['gpt-6-sol', 'gpt-6-luna']) {
const model = codex.models.find((item) => item.modelName === id)
assert.ok(model, `Codex 订阅 Provider 缺少 ${id}`)
assert.equal(model.source, 'preset')
assert.equal(model.extra.reasoning.defaultEffort, 'medium')
}
})
it('GLM-5.3-FlashX 仅加入共享规格,避免假定中国区智谱 API 已提供该 ID', () => {
const provider = JSON.parse(readFileSync(join(ROOT, 'compute', 'providers', 'zhipu.json'), 'utf8'))
const specs = JSON.parse(readFileSync(join(ROOT, 'compute', 'model-specs', 'zhipu.json'), 'utf8')).specs
assert.equal(provider.models.some((item) => item.modelName === 'glm-5.3-flashx'), false)
const spec = specs.find((item) => item.id === 'glm-5.3-flashx')
assert.ok(spec)
assert.deepEqual(spec.match.exact, ['glm-5.3-flashx', 'z-ai/glm-5.3-flashx'])
assert.equal(spec.spec.contextWindow, 1000000)
assert.equal(spec.spec.maxOutputTokens, 128000)
assert.ok(spec.spec.capabilities.includes('vision'))
})
it('GPT Image 2.5 双型号应使用图像生成接入,并与 GPT Image 2 精确区分', () => {
const provider = JSON.parse(readFileSync(join(ROOT, 'compute', 'providers', 'openai.json'), 'utf8'))
const specs = JSON.parse(readFileSync(join(ROOT, 'compute', 'model-specs', 'openai.json'), 'utf8')).specs
for (const id of ['gpt-image-2.5-sunburst', 'gpt-image-2.5-flare']) {
const model = provider.models.find((item) => item.modelName === id)
const spec = specs.find((item) => item.id === id)
assert.ok(model, `OpenAI Provider 缺少 ${id}`)
assert.ok(spec, `model-specs 缺少 ${id}`)
assert.deepEqual(model.serviceType, ['image_gen'])
assert.deepEqual(spec.match.exact, [id])
assert.equal(model.inputPrice, 5)
assert.equal(model.extra.imageInputPrice, 8)
assert.equal(model.outputPrice, 30)
}
const older = specs.find((item) => item.id === 'gpt-image-2')
assert.deepEqual(older.match.exact, ['gpt-image-2'])
assert.equal('patterns' in older.match, false)
})
it('Qwen3.8 Flash 应提供完整的多模态推理规格', () => {
const specFile = JSON.parse(readFileSync(join(ROOT, 'compute', 'model-specs', 'qwen.json'), 'utf8'))
const modelId = 'qwen3.8-flash'
const specs = specFile.specs.filter((item) => item.id === modelId)
assert.equal(specs.length, 1, `model-specs 中 ${modelId} 应且仅应有一条规格`)
const modelSpec = specs[0]
assert.deepEqual(modelSpec.match.exact, [modelId])
assert.equal('patterns' in modelSpec.match, false)
assert.equal(modelSpec.family, modelId)
assert.equal(modelSpec.spec.contextWindow, 1000000)
assert.equal(modelSpec.spec.maxOutputTokens, 131072)
assert.equal(modelSpec.spec.supportsReasoning, true)
assert.equal(modelSpec.spec.releasedAt, '2026-08-26')
assert.ok(modelSpec.spec.capabilities.includes('vision'))
assert.ok(modelSpec.spec.capabilities.includes('video_understanding'))
assert.ok(modelSpec.spec.capabilities.includes('fast'))
assert.deepEqual(modelSpec.spec.serviceType, ['chat', 'reasoning', 'vision'])
assert.equal(modelSpec.spec.extra.thinkingMaxTokens, 262144)
assert.equal(modelSpec.routing.tier, 'lightweight')
assert.equal(modelSpec.routing.reasoning.defaultMode, 'xhigh')
})
it('GLM-5.3-Flash 应在 Coding Plan 中提供强制思考的多模态接入', () => {
const codingPlan = JSON.parse(readFileSync(join(ROOT, 'compute', 'coding-plans', 'zhipu-coding.json'), 'utf8'))
const specFile = JSON.parse(readFileSync(join(ROOT, 'compute', 'model-specs', 'zhipu.json'), 'utf8'))
const modelId = 'glm-5.3-flash'
const model = codingPlan.models.find((item) => item.modelName === modelId)
const modelSpec = specFile.specs.find((item) => item.id === modelId)
assert.ok(model, `智谱 Coding Plan 缺少 ${modelId}`)
assert.equal(model.contextWindow, 1048576)
assert.equal(model.maxOutputTokens, 131072)
assert.ok(model.capabilities.includes('vision'))
assert.ok(model.capabilities.includes('video_understanding'))
assert.deepEqual(model.serviceType, ['chat', 'reasoning', 'vision'])
assert.equal(model.extra.thinkingOnly, true)
assert.deepEqual(model.extra.reasoning.supportedEfforts, ['low', 'high', 'max'])
assert.equal(model.extra.reasoning.defaultEffort, 'max')
assert.ok(modelSpec, `model-specs 缺少 ${modelId}`)
assert.equal(modelSpec.spec.extra.thinkingOnly, true)
assert.deepEqual(modelSpec.routing.reasoning.supportedModes, ['auto', 'low', 'high', 'max'])
})
it('基础模型与后缀版本应使用独立精确规格,避免互相误匹配', () => {
const qwen = JSON.parse(readFileSync(join(ROOT, 'compute', 'model-specs', 'qwen.json'), 'utf8'))
const deepseek = JSON.parse(readFileSync(join(ROOT, 'compute', 'model-specs', 'deepseek.json'), 'utf8'))
const specFor = (file, id) => file.specs.find((item) => item.id === id)
const qwenBase = specFor(qwen, 'qwen3.8-max')
const qwenPreview = specFor(qwen, 'qwen3.8-max-preview')
const deepseekBase = specFor(deepseek, 'deepseek-v4-pro')
const deepseek0813 = specFor(deepseek, 'deepseek-v4-pro-0813')
const deepseekFlash = specFor(deepseek, 'deepseek-v4-flash')
const deepseekFlash0731 = specFor(deepseek, 'deepseek-v4-flash-0731')
for (const [id, modelSpec] of [
['qwen3.8-max', qwenBase],
['qwen3.8-max-preview', qwenPreview],
['deepseek-v4-pro', deepseekBase],
['deepseek-v4-pro-0813', deepseek0813],
['deepseek-v4-flash', deepseekFlash],
['deepseek-v4-flash-0731', deepseekFlash0731],
]) {
assert.ok(modelSpec, `model-specs 缺少 ${id}`)
assert.deepEqual(modelSpec.match.exact, [id])
assert.equal(modelSpec.family, id)
}
assert.equal('patterns' in deepseekBase.match, false)
assert.equal('patterns' in deepseek0813.match, false)
assert.equal('patterns' in deepseekFlash.match, false)
assert.equal('patterns' in deepseekFlash0731.match, false)
assert.equal(qwenBase.spec.contextWindow, qwenPreview.spec.contextWindow)
assert.equal(qwenBase.spec.maxOutputTokens, 131072)
assert.equal(qwenPreview.spec.maxOutputTokens, 65536)
assert.equal(deepseekBase.spec.contextWindow, deepseek0813.spec.contextWindow)
assert.equal(deepseekBase.spec.maxOutputTokens, deepseek0813.spec.maxOutputTokens)
assert.equal(deepseekFlash.spec.contextWindow, deepseekFlash0731.spec.contextWindow)
assert.equal(deepseekFlash.spec.maxOutputTokens, deepseekFlash0731.spec.maxOutputTokens)
})
describe('Anthropic adaptive thinking 代际声明', () => {
// 这些代际收到 thinking.type=enabled + budget_tokens 会直接 400。客户端只在官方
// api.anthropic.com 端点按模型 ID 兜底;desirecore-cloud 等动态 Provider、非官方域名的
// Anthropic Messages 中转只能从 ModelSpec 补全拿到声明,预置 Provider 条目不经补全,
// 两处漏写都会回退成 budget_tokens(desirecore#2940)。Opus 4.6 / Sonnet 4.6 仍接受
// budget_tokens(仅弃用),不在强制范围内。新代际若同样拒绝 budget_tokens,先扩展这里的正则。
const adaptiveGeneration = /^claude-(?:fable|mythos)(?:$|-)|^claude-(?:opus|sonnet)-(?:[5-9]|[1-9]\d)(?:$|-)|^claude-opus-4-[78](?:$|-)/
const thinkingOnlyGeneration = /^claude-(?:fable|mythos)(?:$|-)|^claude-opus-5-5(?:$|-)/
// 与客户端匹配器同口径:小写、去 vendor 前缀、统一分隔符
const normalizeId = (id) => id.toLowerCase().trim().split('/').at(-1).replace(/[._:\s]+/g, '-')
const specsDir = join(ROOT, 'compute', 'model-specs')
const allSpecs = JSON.parse(readFileSync(join(specsDir, '_index.json'), 'utf8')).order
.flatMap((name) => JSON.parse(readFileSync(join(specsDir, `${name}.json`), 'utf8')).specs)
it('规格按代际声明 adaptiveThinking,并标记始终开启思考的模型', () => {
const adaptiveSpecIds = allSpecs.map((item) => item.id).filter((id) => adaptiveGeneration.test(normalizeId(id)))
for (const id of ['claude-fable-5-1', 'claude-opus-5-5', 'claude-opus-5', 'claude-opus-4-8', 'claude-opus-4-7', 'claude-sonnet-5']) {
assert.ok(adaptiveSpecIds.includes(id), `model-specs 缺少 ${id}`)
}
for (const modelSpec of allSpecs) {
const id = normalizeId(modelSpec.id)
const extra = modelSpec.spec.extra ?? {}
assert.equal(
extra.adaptiveThinking === true,
adaptiveGeneration.test(id),
`${modelSpec.id} 的 adaptiveThinking 与代际不符;若它是新的拒绝 budget_tokens 代际,先扩展本测试的代际正则`,
)
if (id.startsWith('claude-')) {
assert.equal(extra.thinkingOnly === true, thinkingOnlyGeneration.test(id), `${modelSpec.id} 的 thinkingOnly 与代际不符`)
}
}
})
it('匹配键与 family 兜底不会把代际声明带给其他代际', () => {
// 客户端依次按 id/exact、pattern、最长 family 前缀匹配,并把规格 extra 写进模型条目
// (精确与 pattern 命中时 cloud 同步直接覆盖)。任何一个键过宽——family 写成 claude-opus、
// pattern 写成 claude-opus-4*——Opus 4.5 等旧型号就会继承 adaptiveThinking,
// 连官方端点都被改写成 adaptive 而 400。pattern 取第一个 * 之前的前缀判断。
const matchKeys = (modelSpec) => [
modelSpec.id,
...(modelSpec.family ? [modelSpec.family] : []),
...(modelSpec.match?.exact ?? []),
...(modelSpec.match?.patterns ?? []).map((pattern) => pattern.split('*')[0]),
].map(normalizeId)
for (const modelSpec of allSpecs) {
const extra = modelSpec.spec.extra ?? {}
for (const key of matchKeys(modelSpec)) {
if (extra.adaptiveThinking === true) {
assert.ok(adaptiveGeneration.test(key), `${modelSpec.id} 声明了 adaptiveThinking,匹配键「${key}」必须收窄到同一代际`)
}
if (extra.thinkingOnly === true && key.startsWith('claude-')) {
assert.ok(thinkingOnlyGeneration.test(key), `${modelSpec.id} 声明了 thinkingOnly,匹配键「${key}」必须收窄到同一代际`)
}
}
}
// 未单独收录的 Opus / Sonnet 版本由同名家族规格保守补全,不带任何代际声明
for (const family of ['claude-opus', 'claude-sonnet']) {
const holders = allSpecs.filter((item) => item.family === family)
assert.deepEqual(holders.map((item) => item.id), [family], `${family} 家族兜底应只由同名规格承接`)
assert.deepEqual(holders[0].spec.extra ?? {}, {}, `${family} 家族兜底规格不得声明代际属性`)
}
})
it('预置 anthropic-messages 条目自行声明 adaptiveThinking', () => {
let checked = 0
for (const dir of [join(ROOT, 'compute', 'providers'), join(ROOT, 'compute', 'coding-plans')]) {
for (const file of readdirSync(dir).filter((name) => name.endsWith('.json') && !name.startsWith('_'))) {
const provider = JSON.parse(readFileSync(join(dir, file), 'utf8'))
for (const model of provider.models ?? []) {
const apiFormat = model.apiFormat ?? provider.apiFormat
if (apiFormat !== 'anthropic-messages') continue
const adaptive = adaptiveGeneration.test(normalizeId(model.apiModelId ?? model.modelName))
if (!adaptive) continue
checked += 1
assert.equal(model.extra?.adaptiveThinking, true, `${file} 的 ${model.modelName} 缺少 extra.adaptiveThinking`)
}
}
}
assert.ok(checked > 0, '未检查到任何 adaptive 代际的预置条目,数据结构可能已变化')
})
})
it('拒绝采样参数 / 强制工具选择的 Anthropic 模型应在规格与 API Key 预置条目中声明', () => {
// 这些模型收到 temperature / top_p / top_k(Fable 5.1 还有 tool_choice any/tool)会直接 400。
// 客户端纯按声明改写出站请求体、不按模型 ID 推断:API Key 预置条目不经 ModelSpec 补全,
// desirecore-cloud 等动态 Provider 只能从精确命中的 ModelSpec 拿到声明,两处漏写都会把参数原样发出。
// 只做单向断言(已知拒绝的代际必须声明),新代际按官方文档补声明即可,不维护反向代际表。
// 订阅(claude-oauth)请求由 Claude CLI 自建,客户端 compat 层对这两类参数显式 fail closed,不依赖声明。
const samplingRemoved = /^claude-(?:fable|mythos)-|^claude-(?:opus|sonnet)-5(?:$|-)|^claude-opus-4-[78](?:$|-)/
const forcedToolChoiceRemoved = /^claude-(?:fable|mythos)-5-1(?:$|-)|^claude-opus-5-5(?:$|-)/
const specFile = JSON.parse(readFileSync(join(ROOT, 'compute', 'model-specs', 'anthropic.json'), 'utf8'))
for (const id of ['claude-fable-5-1', 'claude-opus-5-5', 'claude-opus-5', 'claude-opus-4-8', 'claude-opus-4-7', 'claude-sonnet-5']) {
assert.ok(specFile.specs.some((item) => item.id === id), `model-specs 缺少 ${id}`)
}
for (const modelSpec of specFile.specs) {
const extra = modelSpec.spec.extra ?? {}
if (samplingRemoved.test(modelSpec.id)) {
assert.equal(extra.samplingParametersDeprecated, true, `${modelSpec.id} 缺少 spec.extra.samplingParametersDeprecated`)
assert.notEqual(typeof modelSpec.spec.defaultTemperature, 'number', `${modelSpec.id} 拒绝采样参数,不应声明数值 defaultTemperature`)
}
if (forcedToolChoiceRemoved.test(modelSpec.id)) {
assert.equal(extra.forcedToolChoiceUnsupported, true, `${modelSpec.id} 缺少 spec.extra.forcedToolChoiceUnsupported`)
}
}
const presetDirs = [join(ROOT, 'compute', 'providers'), join(ROOT, 'compute', 'coding-plans')]
for (const dir of presetDirs) {
for (const file of readdirSync(dir).filter((name) => name.endsWith('.json') && !name.startsWith('_'))) {
const provider = JSON.parse(readFileSync(join(dir, file), 'utf8'))
if (provider.credentialSource === 'claude-oauth') continue
for (const model of provider.models ?? []) {
const apiFormat = model.apiFormat ?? provider.apiFormat
const upstreamId = model.apiModelId ?? model.modelName
if (apiFormat !== 'anthropic-messages') continue
if (samplingRemoved.test(upstreamId)) {
assert.equal(model.extra?.samplingParametersDeprecated, true, `${file} 的 ${model.modelName} 缺少 extra.samplingParametersDeprecated`)
}
if (forcedToolChoiceRemoved.test(upstreamId)) {
assert.equal(model.extra?.forcedToolChoiceUnsupported, true, `${file} 的 ${model.modelName} 缺少 extra.forcedToolChoiceUnsupported`)
}
}
}
}
})
it('所有 Provider 应按供应商归属计价,模型来源不覆盖供应商币种', () => {
const expectedCurrencies = {
anthropic: 'USD',
'anthropic-claude': 'USD',
baichuan: 'CNY',
baidu: 'CNY',
cohere: 'USD',
dashscope: 'CNY',
deepseek: 'CNY',
'github-copilot': 'USD',
google: 'USD',
kling: 'CNY',
'local-whisper': 'USD',
minimax: 'CNY',
mistral: 'USD',
moonshot: 'CNY',
ollama: 'USD',
openai: 'USD',
'openai-codex': 'USD',
openrouter: 'USD',
perplexity: 'USD',
siliconflow: 'CNY',
stability: 'USD',
tencent: 'CNY',
volcengine: 'CNY',
xai: 'USD',
xiaomi: 'CNY',
xunfei: 'CNY',
zhipu: 'CNY',
'zhipu-embedding': 'CNY',
}
const dir = join(ROOT, 'compute', 'providers')
const actualProviders = []
for (const file of readdirSync(dir)) {
if (!file.endsWith('.json') || file === '_index.json') continue
const data = JSON.parse(readFileSync(join(dir, file), 'utf8'))
actualProviders.push(data.provider)
assert.ok(
Object.hasOwn(expectedCurrencies, data.provider),
`未给 Provider ${data.provider} 声明计价归属`,
)
assert.equal(
data.priceCurrency,
expectedCurrencies[data.provider],
`${data.label} 应按供应商归属使用 ${expectedCurrencies[data.provider]} 计价`,
)
}
assert.deepEqual(actualProviders.sort(), Object.keys(expectedCurrencies).sort())
})
it('MiniMax 应使用国内开放平台人民币价', () => {
const provider = JSON.parse(readFileSync(join(ROOT, 'compute', 'providers', 'minimax.json'), 'utf8'))
const model = (modelName) => provider.models.find((item) => item.modelName === modelName)
assert.equal(provider.priceCurrency, 'CNY')
assert.deepEqual(
[model('MiniMax-M3').inputPrice, model('MiniMax-M3').outputPrice],
[2.1, 8.4],
)
assert.deepEqual(
[model('MiniMax-M2.7').inputPrice, model('MiniMax-M2.7').outputPrice],
[2.1, 8.4],
)
assert.deepEqual(
[model('MiniMax-M2.7-highspeed').inputPrice, model('MiniMax-M2.7-highspeed').outputPrice],
[4.2, 16.8],
)
assert.equal(model('image-01').extra.pricePerImage, 0.025)
assert.equal(model('speech-2.8-hd').extra.pricePerMillionCharacters, 350)
assert.equal(model('speech-2.8-turbo').extra.pricePerMillionCharacters, 200)
assert.equal(model('music-2.6').extra.pricePerSongUpToFiveMinutes, 1)
})
})
describe('model-specs 智能路由 schema 反例', () => {
const validate = compile('model-spec')
it('拒绝未声明的 routing 字段,避免策略静默分叉', () => {
const data = JSON.parse(readFileSync(join(ROOT, 'compute', 'model-specs', 'openai.json'), 'utf8'))
data.specs.find((spec) => spec.routing).routing.unknownRoutingPolicy = true
assert.equal(validate(data), false)
})
it('拒绝默认 reasoning 不在模型支持集合中的策略', () => {
const data = JSON.parse(readFileSync(join(ROOT, 'compute', 'model-specs', 'openai.json'), 'utf8'))
data.specs.find((spec) => spec.routing).routing.reasoning.defaultMode = 'max'
data.specs.find((spec) => spec.routing).routing.reasoning.supportedModes = ['auto']
assert.equal(validate(data), false)
})
})
// ==================== Runtime 清单反例 ====================
describe('runtime-recommended schema 反例', () => {
const validate = compile('runtime-recommended')
function makeValidManifest() {
return JSON.parse(readFileSync(join(ROOT, 'runtimes', 'recommended.json'), 'utf8'))
}
it('拒绝缺少平台归档(archives 六平台必填)', () => {
const data = makeValidManifest()
delete data.node.archives['win32-arm64']
assert.equal(validate(data), false)
})
it('拒绝非法 sha256(长度/字符集不符)', () => {
const data = makeValidManifest()
data.python.sha256['darwin-arm64'] = 'not-a-sha'
assert.equal(validate(data), false)
})
it('拒绝未知字段(additionalProperties: false 保护老客户端)', () => {
const data = makeValidManifest()
data.unknownField = true
assert.equal(validate(data), false)
})
})
// ==================== Provider schema 反例 ====================
describe('provider schema 反例(防 PR #1 重演)', () => {
const validate = compile('provider')
function makeValidProvider() {
return {
id: 'provider-test-001',
provider: 'test',
label: 'Test',
baseUrl: 'https://api.test.com',
apiKeyRef: 'test',
apiKeyVerified: false,
enabled: false,
status: 'unconfigured',
services: ['chat'],
models: [
{
modelName: 'm1',
displayName: 'M1',
serviceType: ['chat'],
capabilities: ['chat'],
},
],
}
}
it('合法 minimal provider 通过', () => {
assert.equal(validate(makeValidProvider()), true, JSON.stringify(validate.errors))
})
it('拒绝 defaultTemperature: null(PR #1 死锁元凶)', () => {
const data = makeValidProvider()
data.models[0].defaultTemperature = null
assert.equal(validate(data), false)
assert.ok(
validate.errors.some((e) => e.instancePath.endsWith('/defaultTemperature')),
`应有 defaultTemperature 错误,实际:${JSON.stringify(validate.errors)}`,
)
})
it('拒绝 defaultTopP: null', () => {
const data = makeValidProvider()
data.models[0].defaultTopP = null
assert.equal(validate(data), false)
assert.ok(validate.errors.some((e) => e.instancePath.endsWith('/defaultTopP')))
})
it('拒绝 defaultTemperature: "0.7"(string)', () => {
const data = makeValidProvider()
data.models[0].defaultTemperature = '0.7'
assert.equal(validate(data), false)
})
it('接受省略 defaultTemperature/defaultTopP(reasoning 模型推荐做法)', () => {
const data = makeValidProvider()
// 不设这两个字段
assert.equal(validate(data), true, JSON.stringify(validate.errors))
})
it('接受 extra.reasoning 能力矩阵,同时保留 extra 其他扩展字段', () => {
const data = makeValidProvider()
data.models[0].extra = {
reasoning: {
supportedEfforts: ['low', 'medium', 'high', 'xhigh', 'max'],
defaultEffort: 'medium',
},
providerSpecificFlag: true,
}
assert.equal(validate(data), true, JSON.stringify(validate.errors))
})
it('Provider model 的 adaptiveThinking 只接受布尔值', () => {
for (const value of [true, false]) {
const valid = makeValidProvider()
valid.models[0].extra = { adaptiveThinking: value }
assert.equal(validate(valid), true, JSON.stringify(validate.errors))
}
for (const value of ['true', 1, null]) {
const invalid = makeValidProvider()
invalid.models[0].extra = { adaptiveThinking: value }
assert.equal(validate(invalid), false)
}
})
it('Provider model 的 samplingParametersDeprecated / forcedToolChoiceUnsupported 只接受布尔值', () => {
for (const key of ['samplingParametersDeprecated', 'forcedToolChoiceUnsupported']) {
for (const value of [true, false]) {
const valid = makeValidProvider()
valid.models[0].extra = { [key]: value }
assert.equal(validate(valid), true, JSON.stringify(validate.errors))
}
for (const value of ['true', 1, null]) {
const invalid = makeValidProvider()
invalid.models[0].extra = { [key]: value }
assert.equal(validate(invalid), false)
}
}
})
it('严格校验 Provider model 的 native thinking round-trip 能力', () => {
const valid = makeValidProvider()
valid.models[0].extra = {
thinkingRoundTrip: {
enabled: true,
protocol: 'anthropic-messages',
allowEmptySignature: false,
scope: 'active-tool-turn',
},
}
assert.equal(validate(valid), true, JSON.stringify(validate.errors))
for (const brokenCapability of [
{ enabled: true, protocol: 'anthropic-messages', scope: 'active-tool-turn' },
{ enabled: true, protocol: 'openai-completions', allowEmptySignature: false, scope: 'active-tool-turn' },
{ enabled: true, protocol: 'anthropic-messages', allowEmptySignature: false, scope: 'all-history' },
{ enabled: true, protocol: 'anthropic-messages', allowEmptySignature: false, scope: 'active-tool-turn', completed: true },
]) {
const invalid = makeValidProvider()
invalid.models[0].extra = { thinkingRoundTrip: brokenCapability }
assert.equal(validate(invalid), false)
}
})
it('校验 Provider model 的工具轮 thinking 校验口径', () => {
for (const value of ['requires-thinking', 'accepts-thinkless']) {
const valid = makeValidProvider()
valid.models[0].extra = {
thinkingRoundTrip: {
enabled: true,
protocol: 'anthropic-messages',
allowEmptySignature: false,
scope: 'active-tool-turn',
},
thinkingToolTurnValidation: value,
}
assert.equal(validate(valid), true, JSON.stringify(validate.errors))
}
for (const broken of ['accept', true, null]) {
const invalid = makeValidProvider()
invalid.models[0].extra = { thinkingToolTurnValidation: broken }
assert.equal(validate(invalid), false)
}
})
it('拒绝 Ultra 与重复 reasoning effort', () => {
const ultra = makeValidProvider()
ultra.models[0].extra = {
reasoning: { supportedEfforts: ['high', 'ultra'], defaultEffort: 'high' },
}
assert.equal(validate(ultra), false)
const duplicate = makeValidProvider()
duplicate.models[0].extra = {
reasoning: { supportedEfforts: ['high', 'high'], defaultEffort: 'high' },
}
assert.equal(validate(duplicate), false)
})
it('拒绝不在 supportedEfforts 中的 defaultEffort', () => {
const data = makeValidProvider()
data.models[0].extra = {
reasoning: { supportedEfforts: ['low'], defaultEffort: 'high' },
}
assert.equal(validate(data), false)
})
it('接受 defaultTemperature: 0.7(合法 number)', () => {
const data = makeValidProvider()
data.models[0].defaultTemperature = 0.7
data.models[0].defaultTopP = 0.95
assert.equal(validate(data), true, JSON.stringify(validate.errors))
})
it('拒绝 defaultTemperature 超出 [0, 2] 范围', () => {
const data = makeValidProvider()
data.models[0].defaultTemperature = 3.0
assert.equal(validate(data), false)
})
it('拒绝 model 顶层未知字段(additionalProperties: false 保护老客户端)', () => {
const data = makeValidProvider()
data.models[0].brandNewField = 'oops'
assert.equal(validate(data), false)
assert.ok(
validate.errors.some((e) => e.keyword === 'additionalProperties'),
`应有 additionalProperties 错误,实际:${JSON.stringify(validate.errors)}`,
)
})
it('拒绝 provider 顶层未知字段', () => {
const data = makeValidProvider()
data.unknownTopLevelField = 'oops'
assert.equal(validate(data), false)
assert.ok(validate.errors.some((e) => e.keyword === 'additionalProperties'))
})
it('拒绝缺少必填字段(如 model 缺 modelName)', () => {
const data = makeValidProvider()
delete data.models[0].modelName
assert.equal(validate(data), false)
})
it('拒绝非法 status enum', () => {
const data = makeValidProvider()
data.status = 'wrong-status'
assert.equal(validate(data), false)
})
it('拒绝非法 priceCurrency enum(仅允许 USD/CNY)', () => {
const data = makeValidProvider()
data.priceCurrency = 'EUR'
assert.equal(validate(data), false)
})
})
describe('model-spec schema 接入面边界', () => {
const validate = compile('model-spec')
it('拒绝在模型内在规格中声明 reasoning effort', () => {
const data = {
specs: [{
id: 'gpt-test',
spec: {
extra: {
reasoning: {
supportedEfforts: ['low', 'high'],
defaultEffort: 'low',
},
},
},
}],
}
assert.equal(validate(data), false)
})
it('拒绝在模型内在规格中声明接入面的 thinking 回放能力与工具轮校验口径', () => {
for (const extra of [
{
thinkingRoundTrip: {
enabled: true,
protocol: 'anthropic-messages',
allowEmptySignature: false,
scope: 'active-tool-turn',
},
},
{ thinkingToolTurnValidation: 'accepts-thinkless' },
]) {
assert.equal(validate({ specs: [{ id: 'claude-test', spec: { extra } }] }), false)
}
})
it('adaptiveThinking 是模型内在属性,只接受布尔值', () => {
for (const value of [true, false]) {
const data = { specs: [{ id: 'claude-test', spec: { extra: { adaptiveThinking: value } } }] }
assert.equal(validate(data), true, JSON.stringify(validate.errors))
}
for (const value of ['true', 1, null]) {
assert.equal(validate({ specs: [{ id: 'claude-test', spec: { extra: { adaptiveThinking: value } } }] }), false)
}
})
it('samplingParametersDeprecated / forcedToolChoiceUnsupported 是模型内在属性,只接受布尔值', () => {
for (const key of ['samplingParametersDeprecated', 'forcedToolChoiceUnsupported']) {
for (const value of [true, false]) {
const data = { specs: [{ id: 'claude-test', spec: { extra: { [key]: value } } }] }
assert.equal(validate(data), true, JSON.stringify(validate.errors))
}
for (const value of ['true', 1, null]) {
assert.equal(validate({ specs: [{ id: 'claude-test', spec: { extra: { [key]: value } } }] }), false)
}
}
})
it('仍允许模型内在扩展参数', () => {
const data = {
specs: [{ id: 'gpt-test', spec: { extra: { intrinsicBudgetHint: 8192 } } }],
}
assert.equal(validate(data), true, JSON.stringify(validate.errors))
})
it('接受 extra.modelOrigin/speech,同时保持其他扩展开放', () => {
const data = {
specs: [{
id: 'mimo-v2.5-asr',
spec: {
extra: {
modelOrigin: {
vendor: 'xiaomi',
canonicalModelId: 'mimo-v2.5-asr',
family: 'mimo-asr',
},
speech: {
protocol: 'xiaomi-mimo-chat-asr',
protocolVersion: '1',
modes: ['batch', 'streaming'],
inputFormats: ['wav', 'mp3'],
languageHints: true,
},
intrinsicBudgetHint: 8192,
},
},
}],
}
assert.equal(validate(data), true, JSON.stringify(validate.errors))
})
it('严格拒绝非法 speech 二级结构和字段越界', () => {
const invalidProfile = {
specs: [{
id: 'bad-asr',
spec: {
extra: {
speech: {
protocol: 'xiaomi-mimo-chat-asr',
protocolVersion: '1',
modes: ['batch', 'batch'],
},
},
},
}],
}
assert.equal(validate(invalidProfile), false)
const misplaced = {
specs: [{
id: 'bad-asr',
spec: {
speech: {
protocol: 'xiaomi-mimo-chat-asr',
protocolVersion: '1',
modes: ['batch'],
},
},
}],
}
assert.equal(validate(misplaced), false)
})
})
// ==================== Manifest schema ====================
describe('manifest schema', () => {
const validate = compile('manifest')
it('合法 manifest 通过', () => {
assert.equal(
validate({
version: '1.0.0',
presetDataVersion: 30,
updatedAt: '2026-04-25',
description: 'test',
}),
true,
)
})
it('拒绝 presetDataVersion 为 string', () => {
assert.equal(
validate({
version: '1.0.0',
presetDataVersion: '30',
updatedAt: '2026-04-25',
}),
false,
)
})
it('拒绝非法 updatedAt 格式', () => {
assert.equal(
validate({
version: '1.0.0',
presetDataVersion: 30,
updatedAt: '04/25/2026',
}),
false,
)
})
it('拒绝缺少 presetDataVersion', () => {
assert.equal(
validate({ version: '1.0.0', updatedAt: '2026-04-25' }),
false,
)
})
})
// ==================== Service map schema ====================
describe('service-map schema', () => {
const validate = compile('service-map')
it('合法 service-map 通过', () => {
assert.equal(
validate({
chat: { modelName: 'gpt-5-mini', providerId: 'provider-openai-001' },
}),
true,
)
})
it('拒绝缺少 providerId 的条目', () => {
assert.equal(validate({ chat: { modelName: 'gpt-5-mini' } }), false)
})
it('拒绝条目中含未知字段', () => {
assert.equal(
validate({
chat: {
modelName: 'gpt-5-mini',
providerId: 'provider-openai-001',
extra: 'oops',
},
}),
false,
)
})
})
// ==================== Providers-index schema ====================
describe('providers-index schema', () => {
const validate = compile('providers-index')
it('合法 _index.json 通过', () => {
assert.equal(validate({ description: 'x', order: ['openai', 'anthropic'] }), true)
})
it('拒绝 order 含重复元素', () => {
assert.equal(validate({ order: ['openai', 'openai'] }), false)
})
it('拒绝缺少 order', () => {
assert.equal(validate({ description: 'x' }), false)
})
})