TTS: add provider personas

This commit is contained in:
Barron Roth
2026-04-23 07:26:32 -07:00
committed by Ayaan Zaidi
parent 80219ed1b3
commit 0594fa3c4d
39 changed files with 2021 additions and 136 deletions

View File

@@ -56,11 +56,14 @@ import { theme } from "../terminal/theme.js";
import { canonicalizeSpeechProviderId, listSpeechProviders } from "../tts/provider-registry.js";
import {
getTtsProvider,
getTtsPersona,
listTtsPersonas,
listSpeechVoices,
resolveExplicitTtsOverrides,
resolveTtsConfig,
resolveTtsPrefsPath,
setTtsEnabled,
setTtsPersona,
setTtsProvider,
textToSpeech,
} from "../tts/tts.js";
@@ -256,6 +259,13 @@ const CAPABILITY_METADATA: CapabilityMetadata[] = [
flags: ["--local", "--gateway", "--json"],
resultShape: "provider ids, configured state, models, voices",
},
{
id: "tts.personas",
description: "List TTS personas.",
transports: ["local", "gateway"],
flags: ["--local", "--gateway", "--json"],
resultShape: "persona ids, labels, providers, active persona",
},
{
id: "tts.status",
description: "Show gateway-managed TTS state.",
@@ -284,6 +294,13 @@ const CAPABILITY_METADATA: CapabilityMetadata[] = [
flags: ["--provider", "--local", "--gateway", "--json"],
resultShape: "selected provider",
},
{
id: "tts.set-persona",
description: "Set the active TTS persona.",
transports: ["local", "gateway"],
flags: ["--persona", "--off", "--local", "--gateway", "--json"],
resultShape: "selected persona",
},
{
id: "video.generate",
description: "Generate video files with configured video providers.",
@@ -1181,6 +1198,30 @@ async function runTtsProviders(transport: CapabilityTransport) {
};
}
async function runTtsPersonas(transport: CapabilityTransport) {
if (transport === "gateway") {
return await callGateway({
method: "tts.personas",
timeoutMs: 30_000,
});
}
const cfg = loadConfig();
const config = resolveTtsConfig(cfg);
const prefsPath = resolveTtsPrefsPath(config);
const active = getTtsPersona(config, prefsPath);
return {
active: active?.id ?? null,
personas: listTtsPersonas(config).map((persona) => ({
id: persona.id,
label: persona.label,
description: persona.description,
provider: persona.provider,
fallbackPolicy: persona.fallbackPolicy,
providers: Object.keys(persona.providers ?? {}),
})),
};
}
async function runTtsVoices(providerRaw?: string) {
const cfg = loadConfig();
const config = resolveTtsConfig(cfg);
@@ -1194,9 +1235,10 @@ async function runTtsVoices(providerRaw?: string) {
}
async function runTtsStateMutation(params: {
capability: "tts.enable" | "tts.disable" | "tts.set-provider";
capability: "tts.enable" | "tts.disable" | "tts.set-provider" | "tts.set-persona";
transport: CapabilityTransport;
provider?: string;
persona?: string | null;
}) {
if (params.transport === "gateway") {
const method =
@@ -1204,10 +1246,17 @@ async function runTtsStateMutation(params: {
? "tts.enable"
: params.capability === "tts.disable"
? "tts.disable"
: "tts.setProvider";
: params.capability === "tts.set-provider"
? "tts.setProvider"
: "tts.setPersona";
const payload = await callGateway({
method,
params: params.provider ? { provider: params.provider } : undefined,
params:
params.capability === "tts.set-provider"
? { provider: params.provider }
: params.capability === "tts.set-persona"
? { persona: params.persona ?? "off" }
: undefined,
timeoutMs: 30_000,
});
return payload;
@@ -1224,6 +1273,20 @@ async function runTtsStateMutation(params: {
setTtsEnabled(prefsPath, false);
return { enabled: false };
}
if (params.capability === "tts.set-persona") {
if (!params.persona) {
setTtsPersona(prefsPath, null);
return { persona: null };
}
const persona = listTtsPersonas(config).find(
(entry) => entry.id === normalizeLowercaseStringOrEmpty(params.persona ?? ""),
);
if (!persona) {
throw new Error(`Unknown TTS persona: ${params.persona}`);
}
setTtsPersona(prefsPath, persona.id);
return { persona: persona.id };
}
if (!params.provider) {
throw new Error("--provider is required");
}
@@ -1746,6 +1809,27 @@ export function registerCapabilityCli(program: Command) {
});
});
tts
.command("personas")
.description("List TTS personas")
.option("--local", "Force local execution", false)
.option("--gateway", "Force gateway execution", false)
.option("--json", "Output JSON", false)
.action(async (opts) => {
await runCommandWithRuntime(defaultRuntime, async () => {
const transport = resolveTransport({
local: Boolean(opts.local),
gateway: Boolean(opts.gateway),
supported: ["local", "gateway"],
defaultTransport: "local",
});
const result = await runTtsPersonas(transport);
emitJsonOrText(defaultRuntime, Boolean(opts.json), result, (value) =>
JSON.stringify(value, null, 2),
);
});
});
tts
.command("status")
.description("Show TTS status")
@@ -1823,6 +1907,36 @@ export function registerCapabilityCli(program: Command) {
});
});
tts
.command("set-persona")
.description("Set the active TTS persona")
.option("--persona <id>", "TTS persona id")
.option("--off", "Disable the active TTS persona", false)
.option("--local", "Force local execution", false)
.option("--gateway", "Force gateway execution", false)
.option("--json", "Output JSON", false)
.action(async (opts) => {
await runCommandWithRuntime(defaultRuntime, async () => {
const transport = resolveTransport({
local: Boolean(opts.local),
gateway: Boolean(opts.gateway),
supported: ["local", "gateway"],
defaultTransport: "gateway",
});
if (!opts.off && !opts.persona) {
throw new Error("--persona is required unless --off is set");
}
const result = await runTtsStateMutation({
capability: "tts.set-persona",
persona: opts.off ? null : String(opts.persona),
transport,
});
emitJsonOrText(defaultRuntime, Boolean(opts.json), result, (value) =>
JSON.stringify(value, null, 2),
);
});
});
const video = capability.command("video").description("Video generation and description");
video