Capabilities & read-only contract
The host settings page has to know without starting the engine: does this plugin have a voice list? Does it support voice cloning / voice removal / native speed?
How to declare (two equivalent ways; the host takes the union)
Option 1: mod.json (recommended)
{
"capabilities": {
"voices": true,
"voice_clone": true,
"voice_remove": true,
"speed": false
}
}| Key | Meaning | Equivalent class attribute / method |
|---|---|---|
voices | Provides a voice list (the settings page shows the "Voice" dropdown) | Override list_voices() |
voice_clone | Supports registering cloned voices (the settings page shows the "Register cloned voice" panel, including recording reference audio inside the app) | supports_voice_clone = True |
voice_remove | Supports deleting registered voices (a "Delete voice" button appears next to kind="registered" voices only) | Override remove_voice() |
speed | Native speed support (duration prediction inside the model, better quality) | supports_speed = True |
Option 2: class attributes / method overrides
class MyTTS(BaseTTSProvider):
supports_voice_clone = True # supports voice cloning
supports_speed = True # native speed
def list_voices(self): ... # has a voice list
def remove_voice(self, name): ...Read-only query contract (mandatory)
When querying capabilities the host never creates a plugin instance, let alone starts your local service.
The host reads from two sources only:
capabilitiesinmod.json;- your class attributes (it only
imports the module and does not call__init__/initialize).
Two hard requirements follow:
Capabilities must be readable at class level. Assigning them only inside
__init__/initialize→ the host cannot see them while no instance exists. (When the host already has an instance it prefers instance attributes, but you must not rely on that.) 👉 Conclusion: writesupports_voice_clone/supports_speedin the class body, or declare them inmod.json.voices/voice_removecheck whether the method is overridden — an instance attribute cannot override a method.No read-only method may start a service:
list_voices(),check_ready(), and anyget_*/ status query. When the data is not ready, return an empty list /(False, code, msg)and let the host treat that as "not ready".
Where the host probes read-only
- Opening the settings page / switching tabs
- Looking at model card status
- Reading the voice list, checking "supports voice cloning / deletion"
- Restoring the previously active engine (
set_active_provider()) — the active engine is only a config change: no preheat, no load
⚠️ Counter-example (a real bug): putting
_start_server()insideinitialize(). A user who "only opens the settings page or switches to that engine tab" waits seconds for nothing and burns several GB of VRAM. See Local-service engine contract.
Reference implementation
extensions/workshop_common_qwen3_tts (writes capabilities and the class attributes in both places, and none of its read-only paths starts a service).
Related
- tts speech synthesis — full usage of the voice list / cloning / speed
- Local-service engine contract — lazy startup
- Provider signatures & data types
- Model artifacts —
check_ready()semantics
