moeru-ai/airi · error · Error
Invalid model: ${modelId}. Must be one of: ${KOKORO_MODELS.m
Error message
Invalid model: ${modelId}. Must be one of: ${KOKORO_MODELS.map(item => item.id).join(', ')} What it means
assertModelSupported looks modelId up in the static KOKORO_MODELS catalog and throws when it is absent, listing the valid ids in the message. The id typically comes from persisted settings or caller code, so this fires when a stored id no longer exists in the catalog shipped by the running code.
Source
Thrown at packages/stage-ui/src/libs/providers/providers/kokoro-local/index.ts:45
'pt-br': { code: 'pt-BR', title: 'Portuguese (Brazil)' },
}
function getWebGpuState() {
const capabilities = getCachedWebGPUCapabilities()
return {
supported: capabilities?.supported ?? (typeof navigator !== 'undefined' && Boolean(navigator.gpu)),
fp16Supported: capabilities?.fp16Supported ?? false,
}
}
function getModel(modelId: string) {
return KOKORO_MODELS.find(model => model.id === modelId)
}
function assertModelSupported(modelId: string) {
const model = getModel(modelId)
if (!model)
throw new Error(`Invalid model: ${modelId}. Must be one of: ${KOKORO_MODELS.map(item => item.id).join(', ')}`)
if (model.platform === 'webgpu' && !getWebGpuState().supported)
throw new Error('WebGPU is required for this model but is not available in your browser')
return model
}
function progressInfo(progress: { file?: string, percent: number, loaded?: number, total?: number }): ProgressInfo {
return {
name: progress.file ?? '',
file: progress.file ?? '',
progress: progress.percent >= 0 ? progress.percent : 0,
status: 'progress',
loaded: progress.loaded ?? 0,
total: progress.total ?? 0,
}
}
View on GitHub (pinned to 677329427f)
Solutions
- Use one of the ids listed in the error message — it enumerates the current catalog
- Re-select the model in the provider settings UI so the current catalog id is persisted
- Fall back to the first catalog entry when the persisted id is unknown
- Add a settings migration step when model ids change between releases
Example fix
// before
kokoro.speech('kokoro-82m-int8-q4', ...) // id no longer in KOKORO_MODELS
// after
const modelId = KOKORO_MODELS.some(m => m.id === savedId) ? savedId : KOKORO_MODELS[0]!.id
kokoro.speech(modelId, ...) Defensive patterns
Strategy: validation
Validate before calling
const isValid = KOKORO_MODELS.some(m => m.id === selectedModelId) if (!isValid) selectedModelId = KOKORO_MODELS[0]!.id
Type guard
function isKokoroModelId(id: string): boolean {
return KOKORO_MODELS.some(m => m.id === id)
} Try / catch
try {
assertModelSupported(modelId)
}
catch (err) {
if (err.message.startsWith('Invalid model:'))
modelId = KOKORO_MODELS[0]!.id // reset to a valid default and continue
} Prevention
- Derive model pickers from the live catalog, not hardcoded lists
- Add settings migrations when model ids change between releases
- Validate persisted ids on load and reset to defaults when unknown
When it happens
Trigger: A settings record persisted by an older app version references a model id that was renamed or removed; a hardcoded model string has a typo; the catalog changed ids between ONNX and WebGPU variants across releases.
Common situations: App update renamed model variants; importing settings from another machine; manually editing stored provider config.
Related errors
- Unknown Kokoro voice: ${body.voice}
- mutexAcquireTimeout must be a positive finite number
- timeout must be a positive finite number
- Tool input schema must be a JSON Schema object or a Standard
- Extension entrypoint id `${extension.id}` must match manifes
AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18).
Data as JSON: /api/errors/7902c216fb8b6342.
Report an issue: GitHub.