openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
394 lines (393 loc) • 16.9 kB
JavaScript
import { S as parseStrictInteger, s as asFiniteNumber, x as parseStrictFiniteNumber } from "./number-coercion-CLj0HTDM.js";
import { a as asOptionalRecord } from "./record-coerce-DItp3I4t.js";
import { l as normalizeOptionalString, o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.js";
import { n as parseBooleanValue, t as asBoolean } from "./boolean-DmBL0YJK.js";
import { m as normalizeResolvedSecretInputString } from "./types.secrets-kC0nOetj.js";
import { a as resolveSpeechProviderApiKey, i as requireInRange, n as normalizeLanguageCode, r as normalizeSeed, t as normalizeApplyTextNormalization } from "./tts-provider-helpers-Cv20aS12.js";
import "./number-runtime-Cy4drVnh.js";
import "./string-coerce-runtime-GQa0ehRA.js";
import "./secret-input-dpVVFmLG.js";
import "./speech-provider-D6qM0DDB.js";
import { n as isValidElevenLabsVoiceId, r as normalizeElevenLabsBaseUrl } from "./shared-C4zbmF0I.js";
import { r as resolveElevenLabsApiKeyWithProfileFallback } from "./config-compat-D3gex3Bl.js";
import "./config-api-EUFzaTQX.js";
import { n as elevenLabsTTSStream, t as elevenLabsTTS } from "./tts-MKPPgcxn.js";
//#region extensions/elevenlabs/speech-provider-factory.ts
const DEFAULT_ELEVENLABS_VOICE_ID = "pMsXgVXv3BLzUgSXRplE";
const DEFAULT_ELEVENLABS_MODEL_ID = "eleven_multilingual_v2";
const DEFAULT_ELEVENLABS_VOICE_SETTINGS = {
stability: .5,
similarityBoost: .75,
style: 0,
useSpeakerBoost: true,
speed: 1
};
const ELEVENLABS_TTS_MODELS = [
"eleven_v3",
"eleven_multilingual_v2",
"eleven_flash_v2_5",
"eleven_flash_v2",
"eleven_turbo_v2_5",
"eleven_monolingual_v1"
];
function normalizeElevenLabsTtsModelId(value) {
switch (value) {
case "eleven_turbo_v2_5": return "eleven_flash_v2_5";
case "eleven_turbo_v2": return "eleven_flash_v2";
default: return value;
}
}
function parseNumberValue(value) {
return parseStrictFiniteNumber(value);
}
function normalizeVoiceSetting(value, min, max) {
const number = asFiniteNumber(value);
return number !== void 0 && number >= min && number <= max ? number : void 0;
}
function normalizeElevenLabsSeed(value) {
const seed = asFiniteNumber(value);
return seed !== void 0 && Number.isSafeInteger(seed) && seed >= 0 && seed <= 4294967295 ? seed : void 0;
}
function normalizeElevenLabsLatencyTier(value) {
const latencyTier = asFiniteNumber(value);
return latencyTier !== void 0 && Number.isSafeInteger(latencyTier) && latencyTier >= 0 && latencyTier <= 4 ? latencyTier : void 0;
}
const ELEVENLABS_OUTPUT_FAMILIES = /* @__PURE__ */ new Set([
"opus",
"mp3",
"pcm",
"ulaw",
"alaw",
"wav"
]);
function resolveElevenLabsOutputPlan(req) {
const outputFormat = normalizeOptionalString(req.providerOverrides?.outputFormat) ?? (req.target === "voice-note" ? "opus_48000_64" : "mp3_44100_128");
const family = outputFormat.trim().toLowerCase().split("_", 1)[0];
return {
outputFormat,
fileExtension: family && ELEVENLABS_OUTPUT_FAMILIES.has(family) ? `.${family}` : ".bin",
voiceCompatible: family === "opus"
};
}
function normalizeVoiceSettings(rawVoiceSettings) {
return {
...normalizeVoiceSetting(rawVoiceSettings?.stability, 0, 1) == null ? {} : { stability: normalizeVoiceSetting(rawVoiceSettings?.stability, 0, 1) },
...normalizeVoiceSetting(rawVoiceSettings?.similarityBoost, 0, 1) == null ? {} : { similarityBoost: normalizeVoiceSetting(rawVoiceSettings?.similarityBoost, 0, 1) },
...normalizeVoiceSetting(rawVoiceSettings?.style, 0, 1) == null ? {} : { style: normalizeVoiceSetting(rawVoiceSettings?.style, 0, 1) },
...asBoolean(rawVoiceSettings?.useSpeakerBoost) == null ? {} : { useSpeakerBoost: asBoolean(rawVoiceSettings?.useSpeakerBoost) },
...normalizeVoiceSetting(rawVoiceSettings?.speed, .5, 2) == null ? {} : { speed: normalizeVoiceSetting(rawVoiceSettings?.speed, .5, 2) }
};
}
function normalizeElevenLabsProviderConfig(rawConfig) {
const providers = asOptionalRecord(rawConfig.providers);
const raw = asOptionalRecord(providers?.elevenlabs) ?? asOptionalRecord(rawConfig.elevenlabs);
const rawVoiceSettings = asOptionalRecord(raw?.voiceSettings);
return {
apiKey: normalizeResolvedSecretInputString({
value: raw?.apiKey,
path: "tts.providers.elevenlabs.apiKey"
}),
baseUrl: normalizeElevenLabsBaseUrl(normalizeOptionalString(raw?.baseUrl)),
voiceId: normalizeOptionalString(raw?.voiceId) ?? DEFAULT_ELEVENLABS_VOICE_ID,
modelId: normalizeElevenLabsTtsModelId(normalizeOptionalString(raw?.modelId)) ?? DEFAULT_ELEVENLABS_MODEL_ID,
seed: normalizeElevenLabsSeed(raw?.seed),
applyTextNormalization: normalizeOptionalString(raw?.applyTextNormalization),
languageCode: normalizeOptionalString(raw?.languageCode),
voiceSettings: {
...DEFAULT_ELEVENLABS_VOICE_SETTINGS,
...normalizeVoiceSettings(rawVoiceSettings)
}
};
}
function readElevenLabsProviderConfig(config) {
const defaults = normalizeElevenLabsProviderConfig({});
const voiceSettings = asOptionalRecord(config.voiceSettings);
return {
apiKey: normalizeOptionalString(config.apiKey) ?? defaults.apiKey,
baseUrl: normalizeElevenLabsBaseUrl(normalizeOptionalString(config.baseUrl) ?? defaults.baseUrl),
voiceId: normalizeOptionalString(config.voiceId) ?? defaults.voiceId,
modelId: normalizeElevenLabsTtsModelId(normalizeOptionalString(config.modelId)) ?? defaults.modelId,
seed: normalizeElevenLabsSeed(config.seed) ?? defaults.seed,
applyTextNormalization: normalizeOptionalString(config.applyTextNormalization) ?? defaults.applyTextNormalization,
languageCode: normalizeOptionalString(config.languageCode) ?? defaults.languageCode,
voiceSettings: {
...defaults.voiceSettings,
...normalizeVoiceSettings(voiceSettings)
}
};
}
function resolveElevenLabsApiKey(...candidates) {
return resolveSpeechProviderApiKey(...candidates, resolveElevenLabsApiKeyWithProfileFallback() ?? void 0, process.env.XI_API_KEY);
}
function resolveElevenLabsTalkApiKey(config) {
if (config.apiKey === void 0) return resolveElevenLabsApiKey();
return normalizeResolvedSecretInputString({
value: config.apiKey,
path: "talk.providers.elevenlabs.apiKey"
});
}
function mergeVoiceSettingsOverride(ctx, next) {
return {
...ctx.currentOverrides,
voiceSettings: {
...asOptionalRecord(ctx.currentOverrides?.voiceSettings),
...next
}
};
}
function resolveVoiceSettingsOverride(base, overrides) {
const voiceSettings = asOptionalRecord(overrides);
return {
...base,
...normalizeVoiceSettings(voiceSettings)
};
}
function parseDirectiveToken(ctx, formatErrorMessage) {
try {
switch (ctx.key) {
case "voiceid":
case "voice_id":
case "elevenlabs_voice":
case "elevenlabsvoice":
if (!ctx.policy.allowVoice) return { handled: true };
if (!isValidElevenLabsVoiceId(ctx.value)) return {
handled: true,
warnings: [`invalid ElevenLabs voiceId "${ctx.value}"`]
};
return {
handled: true,
overrides: {
...ctx.currentOverrides,
voiceId: ctx.value
}
};
case "model":
case "modelid":
case "model_id":
case "elevenlabs_model":
case "elevenlabsmodel":
if (!ctx.policy.allowModelId) return { handled: true };
return {
handled: true,
overrides: {
...ctx.currentOverrides,
modelId: normalizeElevenLabsTtsModelId(ctx.value)
}
};
case "stability":
case "similarity":
case "similarityboost":
case "similarity_boost":
case "style":
case "speed": {
if (!ctx.policy.allowVoiceSettings) return { handled: true };
const setting = ctx.key.startsWith("similarity") ? "similarityBoost" : ctx.key;
const value = parseNumberValue(ctx.value);
if (value == null) return {
handled: true,
warnings: [`invalid ${setting} value`]
};
requireInRange(value, setting === "speed" ? .5 : 0, setting === "speed" ? 2 : 1, setting);
return {
handled: true,
overrides: mergeVoiceSettingsOverride(ctx, { [setting]: value })
};
}
case "speakerboost":
case "speaker_boost":
case "usespeakerboost":
case "use_speaker_boost": {
if (!ctx.policy.allowVoiceSettings) return { handled: true };
const value = parseBooleanValue(ctx.value);
if (value == null) return {
handled: true,
warnings: ["invalid useSpeakerBoost value"]
};
return {
handled: true,
overrides: mergeVoiceSettingsOverride(ctx, { useSpeakerBoost: value })
};
}
case "normalize":
case "applytextnormalization":
case "apply_text_normalization":
if (!ctx.policy.allowNormalization) return { handled: true };
return {
handled: true,
overrides: {
...ctx.currentOverrides,
applyTextNormalization: normalizeApplyTextNormalization(ctx.value)
}
};
case "language":
case "languagecode":
case "language_code":
if (!ctx.policy.allowNormalization) return { handled: true };
return {
handled: true,
overrides: {
...ctx.currentOverrides,
languageCode: normalizeLanguageCode(ctx.value)
}
};
case "seed":
if (!ctx.policy.allowSeed) return { handled: true };
return {
handled: true,
overrides: {
...ctx.currentOverrides,
seed: normalizeSeed(parseStrictInteger(ctx.value) ?? NaN)
}
};
default: return { handled: false };
}
} catch (error) {
return {
handled: true,
warnings: [formatErrorMessage(error)]
};
}
}
async function listElevenLabsVoices(params) {
const normalizedBaseUrl = normalizeElevenLabsBaseUrl(params.baseUrl);
const { assertOkOrThrowProviderError, readProviderJsonResponse } = await import("./plugin-sdk/provider-http.js");
const { fetchWithSsrFGuard, ssrfPolicyFromHttpBaseUrlAllowedHostname } = await import("./plugin-sdk/ssrf-runtime.js");
const { response, release } = await fetchWithSsrFGuard({
url: `${normalizedBaseUrl}/v1/voices`,
init: { headers: { "xi-api-key": params.apiKey } },
timeoutMs: params.timeoutMs,
policy: ssrfPolicyFromHttpBaseUrlAllowedHostname(normalizedBaseUrl),
auditContext: "elevenlabs.voices"
});
try {
await assertOkOrThrowProviderError(response, "ElevenLabs voices API error");
const json = await readProviderJsonResponse(response, "elevenlabs.voices");
return Array.isArray(json.voices) ? json.voices.map((voice) => ({
id: voice.voice_id?.trim() ?? "",
name: normalizeOptionalString(voice.name),
category: normalizeOptionalString(voice.category),
description: normalizeOptionalString(voice.description)
})).filter((voice) => voice.id.length > 0) : [];
} finally {
await release();
}
}
function resolveElevenLabsTtsRequest(req, options) {
const config = readElevenLabsProviderConfig(req.providerConfig);
const overrides = req.providerOverrides ?? {};
const apiKey = resolveElevenLabsApiKey(config.apiKey);
if (!apiKey) throw new Error("ElevenLabs API key missing");
return {
text: req.text,
apiKey,
baseUrl: config.baseUrl,
voiceId: normalizeOptionalString(overrides.voiceId) ?? config.voiceId,
modelId: normalizeElevenLabsTtsModelId(normalizeOptionalString(overrides.modelId)) ?? config.modelId,
outputFormat: options.outputFormat,
seed: normalizeElevenLabsSeed(overrides.seed) ?? config.seed,
applyTextNormalization: normalizeOptionalString(overrides.applyTextNormalization) ?? config.applyTextNormalization,
languageCode: normalizeOptionalString(overrides.languageCode) ?? config.languageCode,
latencyTier: options.latencyTier,
voiceSettings: resolveVoiceSettingsOverride(config.voiceSettings, overrides.voiceSettings),
timeoutMs: req.timeoutMs
};
}
function buildElevenLabsSpeechProvider({ formatErrorMessage }) {
return {
id: "elevenlabs",
label: "ElevenLabs",
autoSelectOrder: 20,
defaultModel: DEFAULT_ELEVENLABS_MODEL_ID,
models: ELEVENLABS_TTS_MODELS,
resolveConfig: ({ rawConfig }) => normalizeElevenLabsProviderConfig(rawConfig),
parseDirectiveToken: (ctx) => parseDirectiveToken(ctx, formatErrorMessage),
resolveTalkConfig: ({ baseTtsConfig, talkProviderConfig }) => {
const base = normalizeElevenLabsProviderConfig(baseTtsConfig);
const talkVoiceSettings = asOptionalRecord(talkProviderConfig.voiceSettings);
const resolvedTalkApiKey = resolveElevenLabsTalkApiKey(talkProviderConfig);
return {
...base,
...resolvedTalkApiKey === void 0 ? {} : { apiKey: resolvedTalkApiKey },
...normalizeOptionalString(talkProviderConfig.baseUrl) == null ? {} : { baseUrl: normalizeElevenLabsBaseUrl(normalizeOptionalString(talkProviderConfig.baseUrl)) },
...normalizeOptionalString(talkProviderConfig.voiceId) == null ? {} : { voiceId: normalizeOptionalString(talkProviderConfig.voiceId) },
...normalizeOptionalString(talkProviderConfig.modelId) == null ? {} : { modelId: normalizeElevenLabsTtsModelId(normalizeOptionalString(talkProviderConfig.modelId)) },
...normalizeElevenLabsSeed(talkProviderConfig.seed) == null ? {} : { seed: normalizeElevenLabsSeed(talkProviderConfig.seed) },
...normalizeOptionalString(talkProviderConfig.applyTextNormalization) == null ? {} : { applyTextNormalization: normalizeApplyTextNormalization(normalizeOptionalString(talkProviderConfig.applyTextNormalization)) },
...normalizeOptionalString(talkProviderConfig.languageCode) == null ? {} : { languageCode: normalizeLanguageCode(normalizeOptionalString(talkProviderConfig.languageCode)) },
voiceSettings: {
...base.voiceSettings,
...normalizeVoiceSettings(talkVoiceSettings)
}
};
},
resolveTalkOverrides: ({ params }) => {
const normalize = normalizeOptionalString(params.normalize);
const language = normalizeLowercaseStringOrEmpty(normalizeOptionalString(params.language));
const latencyTier = normalizeElevenLabsLatencyTier(params.latencyTier);
const voiceSettings = {
...normalizeVoiceSetting(params.speed, .5, 2) == null ? {} : { speed: normalizeVoiceSetting(params.speed, .5, 2) },
...normalizeVoiceSetting(params.stability, 0, 1) == null ? {} : { stability: normalizeVoiceSetting(params.stability, 0, 1) },
...normalizeVoiceSetting(params.similarity, 0, 1) == null ? {} : { similarityBoost: normalizeVoiceSetting(params.similarity, 0, 1) },
...normalizeVoiceSetting(params.style, 0, 1) == null ? {} : { style: normalizeVoiceSetting(params.style, 0, 1) },
...asBoolean(params.speakerBoost) == null ? {} : { useSpeakerBoost: asBoolean(params.speakerBoost) }
};
return {
...normalizeOptionalString(params.voiceId) == null ? {} : { voiceId: normalizeOptionalString(params.voiceId) },
...normalizeOptionalString(params.modelId) == null ? {} : { modelId: normalizeElevenLabsTtsModelId(normalizeOptionalString(params.modelId)) },
...normalizeOptionalString(params.outputFormat) == null ? {} : { outputFormat: normalizeOptionalString(params.outputFormat) },
...normalizeElevenLabsSeed(params.seed) == null ? {} : { seed: normalizeElevenLabsSeed(params.seed) },
...normalize == null ? {} : { applyTextNormalization: normalizeApplyTextNormalization(normalize) },
...language == null ? {} : { languageCode: normalizeLanguageCode(language) },
...latencyTier == null ? {} : { latencyTier },
...Object.keys(voiceSettings).length === 0 ? {} : { voiceSettings }
};
},
listVoices: async (req) => {
const config = req.providerConfig ? readElevenLabsProviderConfig(req.providerConfig) : void 0;
const requestValue = req.apiKey;
const configValue = config?.apiKey;
const apiKey = resolveElevenLabsApiKey(requestValue, configValue);
if (!apiKey) throw new Error("ElevenLabs API key missing");
return listElevenLabsVoices({
apiKey,
baseUrl: req.baseUrl ?? config?.baseUrl,
timeoutMs: req.timeoutMs
});
},
isConfigured: ({ providerConfig }) => Boolean(resolveElevenLabsApiKey(readElevenLabsProviderConfig(providerConfig).apiKey)),
synthesize: async (req) => {
const overrides = req.providerOverrides ?? {};
const outputPlan = resolveElevenLabsOutputPlan(req);
return {
audioBuffer: await elevenLabsTTS(resolveElevenLabsTtsRequest(req, {
outputFormat: outputPlan.outputFormat,
latencyTier: normalizeElevenLabsLatencyTier(overrides.latencyTier)
})),
...outputPlan
};
},
streamSynthesize: async (req) => {
const overrides = req.providerOverrides ?? {};
const outputPlan = resolveElevenLabsOutputPlan(req);
const stream = await elevenLabsTTSStream(resolveElevenLabsTtsRequest(req, {
outputFormat: outputPlan.outputFormat,
latencyTier: normalizeElevenLabsLatencyTier(overrides.latencyTier)
}));
return {
audioStream: stream.audioStream,
...outputPlan,
release: stream.release
};
},
synthesizeTelephony: async (req) => {
const outputFormat = "pcm_22050";
return {
audioBuffer: await elevenLabsTTS(resolveElevenLabsTtsRequest(req, { outputFormat })),
outputFormat,
sampleRate: 22050
};
}
};
}
//#endregion
export { buildElevenLabsSpeechProvider as t };