can1357/oh-my-pi · error

No local TTS model registered for key: ${modelKey ?? DEFAULT

Error message

No local TTS model registered for key: ${modelKey ?? DEFAULT_TTS_LOCAL_MODEL_KEY}

What it means

resolveTtsRepo maps a local TTS model key to its Hugging Face repo id. The key is looked up in the registered model spec table, falling back to the default key ("kokoro"); if neither the given key nor the default resolves to a spec, it throws. In practice this means the caller passed an unknown model key AND the default spec table is empty/missing the default entry.

Source

Thrown at packages/coding-agent/src/tts/models.ts:122

	value: voice.id,
	label: voice.label,
})) as ReadonlyArray<{ value: string; label: string }>;

/** Accepted `tts.localVoice` values (default model's catalog) for schema validation. */
export const TTS_LOCAL_VOICE_VALUES = KOKORO_VOICES.map(voice => voice.id) as readonly string[];

export function getTtsLocalModelSpec(key: string): TtsLocalModelSpec | undefined {
	return TTS_LOCAL_MODELS.find(model => model.key === key);
}

export function isTtsLocalModelKey(value: string): value is TtsLocalModelKey {
	return getTtsLocalModelSpec(value) !== undefined;
}

/** Resolve a model key (or the default) to its Hugging Face repo id. */
export function resolveTtsRepo(modelKey: string | undefined): string {
	const spec = (modelKey && getTtsLocalModelSpec(modelKey)) || getTtsLocalModelSpec(DEFAULT_TTS_LOCAL_MODEL_KEY);
	if (!spec) throw new Error(`No local TTS model registered for key: ${modelKey ?? DEFAULT_TTS_LOCAL_MODEL_KEY}`);
	return spec.repo;
}

/**
 * Resolve a requested voice id to a concrete voice the model supports, falling
 * back to the model's default voice (first entry) when the id is unknown or the
 * legacy `"default"` sentinel. The returned id is always a valid Kokoro voice.
 */
export function resolveTtsVoice(modelKey: string | undefined, voice: string | undefined): string {
	const spec = (modelKey && getTtsLocalModelSpec(modelKey)) || getTtsLocalModelSpec(DEFAULT_TTS_LOCAL_MODEL_KEY);
	const fallback = spec?.voices[0]?.id ?? DEFAULT_TTS_VOICE;
	if (!spec || !voice) return fallback;
	const match = spec.voices.find(v => v.id === voice);
	return match ? match.id : fallback;
}

View on GitHub (pinned to 9690622007)

Solutions

  1. Use a registered model key (e.g. "kokoro") or unset the config so the default applies
  2. List available keys via the local model spec table (isTtsLocalModel / getTtsLocalModelSpec) and pick a valid one
  3. Check for renames after upgrading — update the tts.localModel setting to the new key
  4. If even the default fails, verify the models.ts spec table is intact (build/registration issue)

Example fix

// before
resolveTtsRepo("kokoro-82m-v1"); // unknown key
// after
resolveTtsRepo("kokoro"); // registered key (or undefined for default)
Defensive patterns

Strategy: fallback

Validate before calling

import { isTtsLocalModelKey } from './models';
const key = isTtsLocalModelKey(config.tts.localModel) ? config.tts.localModel : undefined; // undefined -> default

Type guard

function isValidTtsModelKey(key) {
  return typeof key === 'string' && isTtsLocalModelKey(key);
}

Try / catch

let repo;
try {
  repo = resolveTtsRepo(config.tts.localModel);
} catch (err) {
  if (err.message.includes('No local TTS model registered')) {
    repo = resolveTtsRepo(undefined); // fall back to default model
  } else throw err;
}

Prevention

When it happens

Trigger: Calling resolveTtsRepo("some-model") where "some-model" is not in the local model spec table and getTtsLocalModelSpec(DEFAULT_TTS_LOCAL_MODEL_KEY) also returns undefined; passing undefined relies entirely on the default spec existing.

Common situations: A config sets tts.localModel to a typo'd or renamed key; a build/registration bug leaves the spec table unpopulated; a version upgrade removed or renamed a model key still referenced in user settings.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/ce7de17681f5e615. Report an issue: GitHub.