diff --git a/.github/workflows/linux-build.yml b/.github/workflows/linux-build.yml index 705978b74..a277f49db 100644 --- a/.github/workflows/linux-build.yml +++ b/.github/workflows/linux-build.yml @@ -37,6 +37,11 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Check loader/catalog sync + run: | + python3 tools/check_loader_catalog_sync.py --self-test + python3 tools/check_loader_catalog_sync.py + - name: Install dependencies run: | sudo apt-get update diff --git a/.github/workflows/mac-build.yml b/.github/workflows/mac-build.yml index e27e2874b..99bf8f295 100644 --- a/.github/workflows/mac-build.yml +++ b/.github/workflows/mac-build.yml @@ -26,6 +26,11 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Check loader/catalog sync + run: | + python3 tools/check_loader_catalog_sync.py --self-test + python3 tools/check_loader_catalog_sync.py + - name: Configure run: | cmake -S . -B "$BUILD_DIR" \ diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index 55781fd43..2b4b145af 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -23,6 +23,12 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Check loader/catalog sync + shell: pwsh + run: | + python tools/check_loader_catalog_sync.py --self-test + python tools/check_loader_catalog_sync.py + - name: Build audiocpp_cli shell: pwsh run: | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8a972f697..d57257436 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,6 +20,14 @@ Please check the supported model table in [README.md](README.md) before starting If you want to add support for a model family that is already listed, please focus on improving the existing implementation instead of opening a duplicate port. +When a loader is registered (or parked), keep the **package catalog** in sync. Installable `ModelPackage` entries must not advertise families that `audiocpp_cli --list-loaders` does not expose. Follow the checklist in [docs/maintainers/loader_and_catalog.md](docs/maintainers/loader_and_catalog.md) and run: + +```bash +python3 tools/check_loader_catalog_sync.py --self-test +python3 tools/check_loader_catalog_sync.py +``` + +Do not leave a live Hugging Face `SnapshotSource` for a loader that is commented out of `registry.cpp` — mark it `UnsupportedSource` (or remove it) and update the README package table. Good follow-up work for existing model families includes: - Better CLI or server examples diff --git a/README.md b/README.md index d8ddd03c0..3d2c6b724 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ Community model ports live under `community_models` to make the ownership bounda | **outetts** | TTS, voice cloning | en, ar, zh, nl, fr, de, it, ja, ko, lt, ru, es, pt, be, bn, ka, hu, lv, fa, pl, sw, ta, uk | Mirek [@mirek190](https://github.com/mirek190) | Llama-OuteTTS-1.0-1B TTS and voice cloning support | | **vietneu_tts** | TTS, voice cloning | vi, en | Phuoc [@phuocnguyen90](https://github.com/phuocnguyen90) | [VieNeu-TTS-v3-Turbo](vietneu_tts.md) TTS and voice cloning support | -WIP: Higgs Audio v3 TTS 4B, Fish Audio S2 Pro. +WIP (loaders not registered in this release tree — catalog entries are `UnsupportedSource`): Kokoro 82M bf16, Higgs Audio v3 TTS 4B, Parakeet TDT 0.6B v3, Fish Audio S2 Pro. See [docs/maintainers/loader_and_catalog.md](docs/maintainers/loader_and_catalog.md). PocketTTS language selection is a model-load option. When the model path points at the PocketTTS root, the loader uses `english` unless you pass `--load-option language=`. Kyutai's normal non-English PocketTTS releases are smaller distilled language models intended for the fast PocketTTS path. The `_24l` variants are larger 24-layer, undistilled preview models that can sound better but are slower. Kyutai currently publishes French only as `french_24l`, not as a normal distilled `french` language directory, so French is not listed as a normal PocketTTS language here. @@ -299,7 +299,8 @@ Useful CLI features: - `--help` with `--task` shows task-oriented help - `--help` with `--model ` and optional `--family ` shows model-owned request, session, and load options - `--inspect` prints discovered configs, weights, and capabilities -- `--list-loaders` prints registered model families +- `--list-loaders` prints registered model families (`--json` for the machine-readable contract) +- `python tools/model_manager.py list --json` prints installable packages; keep it synced with loaders ([docs/maintainers/loader_and_catalog.md](docs/maintainers/loader_and_catalog.md)) - `--batch-text-file ` runs one offline request per non-empty line - `--batch-text-dir ` runs one offline request per `.txt`, `.md`, or `.json` file, normalizing each file as one paragraph - `--batch-audio-dir ` runs one offline request per `.wav` @@ -370,7 +371,7 @@ The CLI also exposes the runtime loader catalog with `audiocpp_cli --list-loader Recommended top-level install packages: -`Yes` means Hugging Face has a ready-to-use repo that the framework can download as-is. `No` means the tool must assemble, convert, or post-process files before the framework can use them. +`Yes` means Hugging Face has a ready-to-use repo that the framework can download as-is. `No` means the tool must assemble, convert, or post-process files before the framework can use them. Packages whose loaders are not registered in this release tree are listed as **Unavailable** (see [docs/maintainers/loader_and_catalog.md](docs/maintainers/loader_and_catalog.md)). | Package id | Model | HF ready-to-use repo | |---|---|---| @@ -379,12 +380,13 @@ Recommended top-level install packages: | `citrinet_asr` | Citrinet ASR converted layout | No | | `heartmula` | HeartMuLa | No | | `higgs_audio_stt` | Higgs Audio STT | No | -| `higgs_audio_v3_tts_4b` | Higgs Audio v3 TTS 4B | **Yes** | +| `higgs_audio_v3_tts_4b` | Higgs Audio v3 TTS 4B | Unavailable (loader not in this tree) | | `htdemucs` | HTDemucs | No | | `hviske_asr` | Hviske ASR | **Yes** | | `irodori_tts_500m_v3` | Irodori-TTS 500M v3 | No | | `irodori_tts_600m_v3_voice_design` | Irodori-TTS 600M v3 VoiceDesign | No | -| `kokoro_82m_bf16` | Kokoro 82M bf16 | **Yes** | +| `index_tts2` | IndexTTS-2 | **Yes** | +| `kokoro_82m_bf16` | Kokoro 82M bf16 | Unavailable (loader not in this tree) | | `marblenet_vad` | MarbleNet VAD converted layout | No | | `mel_band_roformer` | Mel-Band RoFormer MLX | **Yes** | | `miocodec_25hz_44k_v2` | MioCodec 25Hz 44.1kHz v2 | No | @@ -397,7 +399,7 @@ Recommended top-level install packages: | `nemotron_asr` | Nemotron ASR | **Yes** | | `omnivoice` | OmniVoice | **Yes** | | `outetts_1_0_1b` | OuteTTS 1.0 1B with IBM DAC codec and Qwen3-aligned voice cloning | No | -| `parakeet_tdt_0_6b_v3` | Parakeet TDT 0.6B v3 | **Yes** | +| `parakeet_tdt_0_6b_v3` | Parakeet TDT 0.6B v3 | Unavailable (loader not in this tree) | | `pocket_tts` | PocketTTS | **Yes** | | `qwen3_asr_0_6b` | Qwen3 ASR 0.6B | **Yes** | | `qwen3_asr_1_7b_hf` | Qwen3 ASR 1.7B HF | **Yes** | @@ -413,6 +415,7 @@ Recommended top-level install packages: | `stable_audio_3_small_sfx` | Stable Audio 3 Small SFX | **Yes** | | `supertonic_3` | Supertonic 3 | **Yes** | | `vevo2` | Vevo2 | No | +| `vietneu_tts_v3_turbo` | VieNeu-TTS v3 Turbo | **Yes** | | `vibevoice_1_5b` | VibeVoice 1.5B | No | | `vibevoice_7b` | VibeVoice 7B | No | | `vibevoice_asr` | VibeVoice ASR | No | diff --git a/docs/maintainers/loader_and_catalog.md b/docs/maintainers/loader_and_catalog.md new file mode 100644 index 000000000..f7e7db0c1 --- /dev/null +++ b/docs/maintainers/loader_and_catalog.md @@ -0,0 +1,106 @@ +# Maintaining Loaders and the Package Catalog + +Integrators (CLI users, servers, and UIs such as Studio) treat two exports as +**authoritative**: + +1. **Runtime loaders** — `audiocpp_cli --list-loaders --json` +2. **Install packages** — `python tools/model_manager.py list --json` + +Those surfaces must stay in sync. A package that is installable in the catalog +but whose `family` is missing from `--list-loaders` looks available to users and +then fails at runtime or in search/install UIs. + +## The rule + +For every **installable, standalone** `ModelPackage`: + +| Field | Must match | +|---|---| +| `ModelPackage.family` | The loader family string advertised by the C++ loader | +| `model_specs/.json` | Present when the family uses package-spec loading | +| `registry.cpp` entry | Uncommented `make__loader()` (or the family's actual factory name) | +| README package table | Lists the package; use **Unavailable** when not installable | + +Dependency / subcomponent packages (`standalone=False`, with +`parent_package_id`) do **not** need their own loader. + +Registered loaders that ship as bundled assets (no downloadable package) are +allowed. List them in `BUNDLED_LOADERS_WITHOUT_PACKAGE` inside +`tools/check_loader_catalog_sync.py`. + +If a loader is not ready for this release tree: + +1. Keep it **commented out** in `src/framework/runtime/registry.cpp`, and +2. Mark matching catalog packages as `UnsupportedSource(reason=...)`, **or** + remove them from `CATALOG`, and +3. Mark the README package row **Unavailable**. + +Do **not** leave a live `SnapshotSource` for a commented-out loader. + +Optional catalog↔registry family renames for parked stubs go in +`PARKED_FAMILY_ALIASES` in the sync check (collapse to one id when re-enabling). + +## Checklist: adding a model family + +1. Implement `include/engine/models//` (or `community_models/`) with a + loader that overrides `advertised_capabilities()` so tasks/endpoints are + explicit. +2. Register it in `src/framework/runtime/registry.cpp` (include + + `available_loaders` entry). Prefer the factory name + `make__loader()` so the id matches the advertised family. +3. Add `model_specs/.json` when the family needs package-spec discovery. +4. Add one or more `ModelPackage` entries in `tools/model_manager.py`: + - Set `family=""` explicitly when the package id does not strip cleanly + to the loader id. + - Set `tasks=(...)` when defaults would be ambiguous. + - Use `standalone=False` + `parent_package_id` for tokenizers / subcomponents. +5. Update README supported-model / package tables. +6. Run: + +```bash +python3 tools/check_loader_catalog_sync.py --self-test +python3 tools/check_loader_catalog_sync.py +# after building: +build/.../bin/audiocpp_cli --list-loaders --json +python3 tools/model_manager.py list --json +``` + +Confirm the new family appears in `--list-loaders` and that installable packages +for that family set `"family"` to the same string. + +## Checklist: parking or removing a family + +1. Comment out the include and `make_*_loader()` entry in `registry.cpp`. +2. Convert related **standalone** packages to `UnsupportedSource` with a reason + that names the missing loader and points at this doc (or delete them). +3. Leave `family=` / `tasks=` on unsupported entries if useful for history. +4. Update README so the package row says **Unavailable**. +5. Run `python3 tools/check_loader_catalog_sync.py`. + +## Family id consistency + +Pick **one** family string and use it everywhere: + +- C++ loader / `advertised_capabilities()` +- `make__loader()` naming (when practical) +- `model_specs/.json` +- `ModelPackage.family` +- README “Supported Models” family column + +Integrators match on the string; aliases are not implied unless listed in +`PARKED_FAMILY_ALIASES` for currently parked stubs. + +## CI + +`tools/check_loader_catalog_sync.py` runs in GitHub Actions on Linux/macOS/Windows +builds. It: + +- Parses active vs commented `make_*_loader()` calls in `registry.cpp` +- Compares them to installable standalone packages from `model_manager.py` +- Cross-checks the README recommended package table +- Does **not** require a compiled binary + +```bash +python3 tools/check_loader_catalog_sync.py --self-test +python3 tools/check_loader_catalog_sync.py +``` diff --git a/docs/usage.md b/docs/usage.md index cbbff9c98..5a02835bc 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -11,7 +11,7 @@ audiocpp_cli --task --family --model --backend & config_path) { const std::vector> available_loaders = { - // Development registry entries from Share/AudioCPP that are not present in this release tree yet: + // Parked loaders — keep catalog packages UnsupportedSource while these stay commented. + // See docs/maintainers/loader_and_catalog.md. // engine::models::kokoro_tts::make_kokoro_tts_loader(), // engine::models::higgs_tts::make_higgs_tts_loader(), // engine::models::parakeet_tdt::make_parakeet_tdt_loader(), diff --git a/tools/check_loader_catalog_sync.py b/tools/check_loader_catalog_sync.py new file mode 100644 index 000000000..19069f64e --- /dev/null +++ b/tools/check_loader_catalog_sync.py @@ -0,0 +1,357 @@ +#!/usr/bin/env python3 +"""Fail when installable catalog packages reference unregistered loaders. + +``model_manager list --json`` family fields and ``audiocpp_cli --list-loaders`` +must stay aligned. Installable standalone packages cannot advertise a family +that is missing or commented out in ``src/framework/runtime/registry.cpp``. + +See docs/maintainers/loader_and_catalog.md. +""" + +from __future__ import annotations + +import argparse +import re +import sys +import unittest +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[1] +REGISTRY_PATH = REPO_ROOT / "src" / "framework" / "runtime" / "registry.cpp" +MODEL_MANAGER_PATH = REPO_ROOT / "tools" / "model_manager.py" +README_PATH = REPO_ROOT / "README.md" + +_LOADER_CALL_RE = re.compile(r"\bmake_([a-z0-9_]+)_loader\s*\(\s*\)") + +# Optional: catalog family strings that map to a differently named parked stub. +PARKED_FAMILY_ALIASES: dict[str, set[str]] = { + "higgs_tts": {"higgs_audio_tts"}, +} + +# Registered loaders that intentionally have no installable ModelPackage. +BUNDLED_LOADERS_WITHOUT_PACKAGE: set[str] = { + "silero_vad", +} + + +def parse_registry_loaders(registry_text: str) -> tuple[set[str], set[str]]: + """Return (active_families, commented_families) from registry.cpp.""" + active: set[str] = set() + commented: set[str] = set() + for raw_line in registry_text.splitlines(): + line = raw_line.strip() + match = _LOADER_CALL_RE.search(line) + if not match: + continue + family = match.group(1) + if line.startswith("//"): + commented.add(family) + else: + active.add(family) + return active, commented + + +def family_is_registered(family: str, active: set[str]) -> bool: + return family in active + + +def family_is_parked(family: str, commented: set[str]) -> bool: + if family in commented: + return True + for stub, aliases in PARKED_FAMILY_ALIASES.items(): + if stub in commented and family in aliases: + return True + return False + + +def parked_hint(family: str, commented: set[str]) -> str: + if family in commented: + return " (commented out in registry.cpp)" + for stub, aliases in PARKED_FAMILY_ALIASES.items(): + if stub in commented and family in aliases: + return ( + f" (parked registry stub is '{stub}'; catalog family '{family}' " + "must stay UnsupportedSource until one id is registered)" + ) + return "" + + +def parse_readme_package_table(readme_text: str) -> dict[str, str]: + """Map package id -> status cell from the recommended install table.""" + match = re.search( + r"\| Package id \| Model \| HF ready-to-use repo \|\n\|[^\n]+\n((?:\|.*\n)+)", + readme_text, + ) + if not match: + return {} + rows: dict[str, str] = {} + for line in match.group(1).splitlines(): + cols = [c.strip() for c in line.strip().strip("|").split("|")] + if len(cols) < 3 or not cols[0].startswith("`"): + continue + package_id = cols[0].strip("`") + rows[package_id] = cols[2] + return rows + + +def check_catalog( + *, + active: set[str], + commented: set[str], + packages: list, + package_payload, + default_family_from_package_id, +) -> tuple[list[str], list[str]]: + errors: list[str] = [] + warnings: list[str] = [] + installable_families: set[str] = set() + + for package in packages: + payload = package_payload(package) + package_id = str(payload.get("id") or "") + family = str(payload.get("family") or "").strip() + installable = bool(payload.get("installable")) + standalone = bool(payload.get("standalone", True)) + source = payload.get("source") if isinstance(payload.get("source"), dict) else {} + source_kind = str(source.get("kind") or "") + inferred = default_family_from_package_id(package_id) + explicit = getattr(package, "family", None) + + if not family: + errors.append( + f"{package_id}: missing family (set ModelPackage.family or fix id)" + ) + continue + + if not installable or source_kind == "unsupported": + if family_is_registered(family, active): + warnings.append( + f"{package_id}: UnsupportedSource but family '{family}' is already " + "registered — restore a real SnapshotSource/Composite/Converter" + ) + elif not family_is_parked(family, commented): + warnings.append( + f"{package_id}: UnsupportedSource family '{family}' is neither " + "registered nor a parked registry stub/alias" + ) + continue + + if not standalone: + continue + + installable_families.add(family) + + if explicit is None and inferred != family: + errors.append( + f"{package_id}: resolved family '{family}' != id inference " + f"'{inferred}' without ModelPackage.family set" + ) + + if explicit is None and inferred not in active and family in active: + errors.append( + f"{package_id}: set ModelPackage.family explicitly " + f"(id inference '{inferred}' is not a registered loader)" + ) + + if not family_is_registered(family, active): + errors.append( + f"{package_id}: installable standalone package family '{family}' " + f"is not registered in registry.cpp{parked_hint(family, commented)}" + ) + elif family_is_parked(family, commented): + errors.append( + f"{package_id}: family '{family}' is both active and commented in registry.cpp" + ) + + for stub in sorted(commented): + aliases = {stub} | PARKED_FAMILY_ALIASES.get(stub, set()) + leaked = sorted(fam for fam in aliases if fam in installable_families) + if leaked: + errors.append( + f"parked loader '{stub}' still has installable catalog families: " + + ", ".join(leaked) + ) + + for family in sorted(active): + if family in BUNDLED_LOADERS_WITHOUT_PACKAGE: + continue + if family not in installable_families: + warnings.append( + f"registered loader '{family}' has no installable standalone " + "ModelPackage (add a package or list it in " + "BUNDLED_LOADERS_WITHOUT_PACKAGE if intentional)" + ) + + return errors, warnings + + +def check_readme( + *, + readme_text: str, + packages: list, + package_payload, +) -> tuple[list[str], list[str]]: + errors: list[str] = [] + warnings: list[str] = [] + table = parse_readme_package_table(readme_text) + if not table: + errors.append("README.md: could not parse recommended package table") + return errors, warnings + + catalog_by_id = {} + for package in packages: + payload = package_payload(package) + catalog_by_id[str(payload["id"])] = payload + + for package_id, status in sorted(table.items()): + payload = catalog_by_id.get(package_id) + if payload is None: + errors.append(f"README.md: package `{package_id}` not in model_manager CATALOG") + continue + installable = bool(payload.get("installable")) + unavailable = "unavailable" in status.lower() + if unavailable and installable: + errors.append( + f"README.md: `{package_id}` marked Unavailable but catalog is installable" + ) + if not unavailable and not installable: + errors.append( + f"README.md: `{package_id}` looks installable in the table but catalog " + "is UnsupportedSource — mark Unavailable or restore a source" + ) + + for package_id, payload in sorted(catalog_by_id.items()): + if not payload.get("installable") or not payload.get("standalone", True): + continue + if package_id not in table: + errors.append( + f"README.md: installable standalone package `{package_id}` missing " + "from recommended package table" + ) + + return errors, warnings + + +class _SyncCheckSelfTests(unittest.TestCase): + def test_parse_active_and_commented(self) -> None: + text = """ + // make_family_a_loader(), + make_family_b_loader(), + make_family_c_loader(), // trailing comment still active + """ + active, commented = parse_registry_loaders(text) + self.assertEqual(active, {"family_b", "family_c"}) + self.assertEqual(commented, {"family_a"}) + + def test_parked_alias_blocks_installable(self) -> None: + class Pkg: + def __init__(self, family=None): + self.family = family + + stub = next(iter(PARKED_FAMILY_ALIASES)) + alias = next(iter(PARKED_FAMILY_ALIASES[stub])) + + def payload(package): + return { + "id": "pkg_alias", + "family": alias, + "installable": True, + "standalone": True, + "source": {"kind": "huggingface_snapshot"}, + } + + errors, _ = check_catalog( + active={"family_b"}, + commented={stub}, + packages=[Pkg(family=alias)], + package_payload=payload, + default_family_from_package_id=lambda _pid: "pkg_alias", + ) + self.assertTrue(any(alias in e for e in errors)) + self.assertTrue(any(f"parked loader '{stub}'" in e for e in errors)) + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--registry", + type=Path, + default=REGISTRY_PATH, + help="Path to registry.cpp", + ) + parser.add_argument( + "--skip-readme", + action="store_true", + help="Skip README package-table cross-check", + ) + parser.add_argument( + "--self-test", + action="store_true", + help="Run built-in unit tests and exit", + ) + args = parser.parse_args() + + if args.self_test: + suite = unittest.defaultTestLoader.loadTestsFromTestCase(_SyncCheckSelfTests) + result = unittest.TextTestRunner(verbosity=2).run(suite) + return 0 if result.wasSuccessful() else 1 + + if not args.registry.is_file(): + print(f"error: registry not found: {args.registry}", file=sys.stderr) + return 2 + if not MODEL_MANAGER_PATH.is_file(): + print(f"error: model manager not found: {MODEL_MANAGER_PATH}", file=sys.stderr) + return 2 + + sys.path.insert(0, str(MODEL_MANAGER_PATH.parent)) + import model_manager as mm # noqa: E402 + + active, commented = parse_registry_loaders(args.registry.read_text(encoding="utf-8")) + if not active: + print("error: no active loaders parsed from registry.cpp", file=sys.stderr) + return 2 + + errors, warnings = check_catalog( + active=active, + commented=commented, + packages=list(mm.CATALOG), + package_payload=mm.package_payload, + default_family_from_package_id=mm._default_family_from_package_id, + ) + + if not args.skip_readme: + if not README_PATH.is_file(): + errors.append(f"README.md not found: {README_PATH}") + else: + readme_errors, readme_warnings = check_readme( + readme_text=README_PATH.read_text(encoding="utf-8"), + packages=list(mm.CATALOG), + package_payload=mm.package_payload, + ) + errors.extend(readme_errors) + warnings.extend(readme_warnings) + + print( + f"active_loaders={len(active)} commented_loaders={len(commented)} " + f"catalog_packages={len(mm.CATALOG)}" + ) + for warning in warnings: + print(f"warning: {warning}") + if errors: + print("loader/catalog sync failed:", file=sys.stderr) + for error in errors: + print(f" - {error}", file=sys.stderr) + print( + "\nFix: register the loader in registry.cpp (with sources in-tree), " + "or mark the package UnsupportedSource / update README. See " + "docs/maintainers/loader_and_catalog.md", + file=sys.stderr, + ) + return 1 + + print("ok: installable catalog families match registered loaders") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/model_manager.py b/tools/model_manager.py index fa6930392..9af409492 100644 --- a/tools/model_manager.py +++ b/tools/model_manager.py @@ -220,7 +220,14 @@ def package_usage_examples(package: ModelPackage) -> list[str]: id="kokoro_82m_bf16", display_name="Kokoro 82M bf16", target_directory="Kokoro-82M-bf16", - source=SnapshotSource(repo_id="mlx-community/Kokoro-82M-bf16"), + source=UnsupportedSource( + reason=( + "kokoro_tts loader is not registered in this release tree yet " + "(commented out in src/framework/runtime/registry.cpp). " + "Re-enable the loader, add model_specs/kokoro_tts.json, then " + "restore a SnapshotSource here." + ), + ), required_files=("config.json", "kokoro-v1_0.safetensors", "voices/af_heart.safetensors"), family="kokoro_tts", tasks=("tts",), @@ -515,6 +522,8 @@ def package_usage_examples(package: ModelPackage) -> list[str]: "special_tokens_map.json", ), description="Installs VieNeu-TTS v3 Turbo GGUF model and configuration sidecars for C++ inference.", + family="vietneu_tts", + tasks=("tts",), ), ModelPackage( id="qwen3_tts_1_7b_base", @@ -600,8 +609,17 @@ def package_usage_examples(package: ModelPackage) -> list[str]: id="parakeet_tdt_0_6b_v3", display_name="Parakeet TDT 0.6B v3", target_directory="parakeet-tdt-0.6b-v3", - source=SnapshotSource(repo_id="nvidia/parakeet-tdt-0.6b-v3"), + source=UnsupportedSource( + reason=( + "parakeet_tdt loader is not registered in this release tree yet " + "(commented out in src/framework/runtime/registry.cpp). " + "Re-enable the loader, add model_specs/parakeet_tdt.json, then " + "restore a SnapshotSource here." + ), + ), required_files=("config.json", "model.safetensors", "processor_config.json", "tokenizer.json"), + family="parakeet_tdt", + tasks=("asr",), ), ModelPackage( id="pocket_tts", @@ -828,7 +846,15 @@ def package_usage_examples(package: ModelPackage) -> list[str]: id="higgs_audio_v3_tts_4b", display_name="Higgs Audio v3 TTS 4B", target_directory="higgs-audio-v3-tts-4b", - source=SnapshotSource(repo_id="bosonai/higgs-audio-v3-tts-4b"), + source=UnsupportedSource( + reason=( + "higgs_tts / higgs_audio_tts loader is not registered in this " + "release tree yet (commented out in " + "src/framework/runtime/registry.cpp). Re-enable the loader with " + "one consistent family id, add the matching model_specs entry, " + "then restore a SnapshotSource here." + ), + ), required_files=( "chat_template.jinja", "config.json",