UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

439 lines (438 loc) 18.8 kB
import { a as normalizeLowercaseStringOrEmpty, c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js"; import { _ as parseStrictFiniteNumber, s as asFiniteNumber, v as parseStrictInteger } from "./number-coercion-CJQ8TR--.js"; import { i as formatErrorMessage } from "./errors-BXgSefBE.js"; import { u as normalizeResolvedSecretInputString } from "./types.secrets-_0JOMGE5.js"; import { y as ssrfPolicyFromHttpBaseUrlAllowedHostname } from "./ssrf-CvPEXMGn.js"; import { r as fetchWithSsrFGuard } from "./fetch-guard-BttkNCLm.js"; import { t as asBoolean } from "./boolean-By0bZpFH.js"; import { i as assertOkOrThrowProviderError, n as asObject } from "./provider-http-errors-DqaqQLLZ.js"; import "./error-runtime-C8vbtAJt.js"; import "./number-runtime-DBLVDypr.js"; import "./string-coerce-runtime-CEGJWkQ_.js"; import { i as requireInRange, n as normalizeLanguageCode, r as normalizeSeed, t as normalizeApplyTextNormalization } from "./tts-provider-helpers-DcnFWueT.js"; import "./secret-input-DVCzFGxN.js"; import "./ssrf-runtime-BOGN5pUi.js"; import "./provider-http-BXCBCi4E.js"; import "./speech-DeKvI3F1.js"; import { n as isValidElevenLabsVoiceId, r as normalizeElevenLabsBaseUrl } from "./shared-B7utk7gS.js"; import { r as resolveElevenLabsApiKeyWithProfileFallback } from "./config-compat-CPWT0Een.js"; import "./config-api-DABeooPy.js"; import { n as elevenLabsTTSStream, t as elevenLabsTTS } from "./tts-BH7PvdN5.js"; //#region extensions/elevenlabs/speech-provider.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_turbo_v2_5", "eleven_monolingual_v1" ]; function parseBooleanValue(value) { const normalized = normalizeLowercaseStringOrEmpty(value); if ([ "true", "1", "yes", "on" ].includes(normalized)) return true; if ([ "false", "0", "no", "off" ].includes(normalized)) return false; } function parseNumberValue(value) { return parseStrictFiniteNumber(value); } const isValidVoiceId = isValidElevenLabsVoiceId; 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; } 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 raw = asObject(asObject(rawConfig.providers)?.elevenlabs) ?? asObject(rawConfig.elevenlabs); const rawVoiceSettings = asObject(raw?.voiceSettings); return { apiKey: normalizeResolvedSecretInputString({ value: raw?.apiKey, path: "messages.tts.providers.elevenlabs.apiKey" }), baseUrl: normalizeElevenLabsBaseUrl(normalizeOptionalString(raw?.baseUrl)), voiceId: normalizeOptionalString(raw?.voiceId) ?? DEFAULT_ELEVENLABS_VOICE_ID, modelId: 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 = asObject(config.voiceSettings); return { apiKey: normalizeOptionalString(config.apiKey) ?? defaults.apiKey, baseUrl: normalizeElevenLabsBaseUrl(normalizeOptionalString(config.baseUrl) ?? defaults.baseUrl), voiceId: normalizeOptionalString(config.voiceId) ?? defaults.voiceId, modelId: 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 mergeVoiceSettingsOverride(ctx, next) { return { ...ctx.currentOverrides, voiceSettings: { ...asObject(ctx.currentOverrides?.voiceSettings), ...next } }; } function resolveVoiceSettingsOverride(base, overrides) { const voiceSettings = asObject(overrides); return { ...base, ...normalizeVoiceSettings(voiceSettings) }; } function parseDirectiveToken(ctx) { 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: ctx.value } }; case "stability": { if (!ctx.policy.allowVoiceSettings) return { handled: true }; const value = parseNumberValue(ctx.value); if (value == null) return { handled: true, warnings: ["invalid stability value"] }; requireInRange(value, 0, 1, "stability"); return { handled: true, overrides: mergeVoiceSettingsOverride(ctx, { stability: value }) }; } case "similarity": case "similarityboost": case "similarity_boost": { if (!ctx.policy.allowVoiceSettings) return { handled: true }; const value = parseNumberValue(ctx.value); if (value == null) return { handled: true, warnings: ["invalid similarityBoost value"] }; requireInRange(value, 0, 1, "similarityBoost"); return { handled: true, overrides: mergeVoiceSettingsOverride(ctx, { similarityBoost: value }) }; } case "style": { if (!ctx.policy.allowVoiceSettings) return { handled: true }; const value = parseNumberValue(ctx.value); if (value == null) return { handled: true, warnings: ["invalid style value"] }; requireInRange(value, 0, 1, "style"); return { handled: true, overrides: mergeVoiceSettingsOverride(ctx, { style: value }) }; } case "speed": { if (!ctx.policy.allowVoiceSettings) return { handled: true }; const value = parseNumberValue(ctx.value); if (value == null) return { handled: true, warnings: ["invalid speed value"] }; requireInRange(value, .5, 2, "speed"); return { handled: true, overrides: mergeVoiceSettingsOverride(ctx, { speed: 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 { response, release } = await fetchWithSsrFGuard({ url: `${normalizedBaseUrl}/v1/voices`, init: { headers: { "xi-api-key": params.apiKey } }, policy: ssrfPolicyFromHttpBaseUrlAllowedHostname(normalizedBaseUrl), auditContext: "elevenlabs.voices" }); try { await assertOkOrThrowProviderError(response, "ElevenLabs voices API error"); const json = await response.json(); 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 buildElevenLabsSpeechProvider() { return { id: "elevenlabs", label: "ElevenLabs", autoSelectOrder: 20, defaultModel: DEFAULT_ELEVENLABS_MODEL_ID, models: ELEVENLABS_TTS_MODELS, resolveConfig: ({ rawConfig }) => normalizeElevenLabsProviderConfig(rawConfig), parseDirectiveToken, resolveTalkConfig: ({ baseTtsConfig, talkProviderConfig }) => { const base = normalizeElevenLabsProviderConfig(baseTtsConfig); const talkVoiceSettings = asObject(talkProviderConfig.voiceSettings); const resolvedTalkApiKey = talkProviderConfig.apiKey === void 0 ? resolveElevenLabsApiKeyWithProfileFallback() ?? void 0 : normalizeResolvedSecretInputString({ value: talkProviderConfig.apiKey, path: "talk.providers.elevenlabs.apiKey" }); 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: 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: 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 apiKey = req.apiKey || config?.apiKey || resolveElevenLabsApiKeyWithProfileFallback() || process.env.XI_API_KEY; if (!apiKey) throw new Error("ElevenLabs API key missing"); return listElevenLabsVoices({ apiKey, baseUrl: req.baseUrl ?? config?.baseUrl }); }, isConfigured: ({ providerConfig }) => Boolean(readElevenLabsProviderConfig(providerConfig).apiKey || resolveElevenLabsApiKeyWithProfileFallback() || process.env.XI_API_KEY), synthesize: async (req) => { const config = readElevenLabsProviderConfig(req.providerConfig); const overrides = req.providerOverrides ?? {}; const apiKey = config.apiKey || resolveElevenLabsApiKeyWithProfileFallback() || process.env.XI_API_KEY; if (!apiKey) throw new Error("ElevenLabs API key missing"); const outputFormat = normalizeOptionalString(overrides.outputFormat) ?? (req.target === "voice-note" ? "opus_48000_64" : "mp3_44100_128"); const latencyTier = normalizeElevenLabsLatencyTier(overrides.latencyTier); return { audioBuffer: await elevenLabsTTS({ text: req.text, apiKey, baseUrl: config.baseUrl, voiceId: normalizeOptionalString(overrides.voiceId) ?? config.voiceId, modelId: normalizeOptionalString(overrides.modelId) ?? config.modelId, outputFormat, seed: normalizeElevenLabsSeed(overrides.seed) ?? config.seed, applyTextNormalization: normalizeOptionalString(overrides.applyTextNormalization) ?? config.applyTextNormalization, languageCode: normalizeOptionalString(overrides.languageCode) ?? config.languageCode, latencyTier, voiceSettings: resolveVoiceSettingsOverride(config.voiceSettings, overrides.voiceSettings), timeoutMs: req.timeoutMs }), outputFormat, fileExtension: req.target === "voice-note" ? ".opus" : ".mp3", voiceCompatible: req.target === "voice-note" }; }, streamSynthesize: async (req) => { const config = readElevenLabsProviderConfig(req.providerConfig); const overrides = req.providerOverrides ?? {}; const apiKey = config.apiKey || resolveElevenLabsApiKeyWithProfileFallback() || process.env.XI_API_KEY; if (!apiKey) throw new Error("ElevenLabs API key missing"); const outputFormat = normalizeOptionalString(overrides.outputFormat) ?? (req.target === "voice-note" ? "opus_48000_64" : "mp3_44100_128"); const latencyTier = normalizeElevenLabsLatencyTier(overrides.latencyTier); const stream = await elevenLabsTTSStream({ text: req.text, apiKey, baseUrl: config.baseUrl, voiceId: normalizeOptionalString(overrides.voiceId) ?? config.voiceId, modelId: normalizeOptionalString(overrides.modelId) ?? config.modelId, outputFormat, seed: normalizeElevenLabsSeed(overrides.seed) ?? config.seed, applyTextNormalization: normalizeOptionalString(overrides.applyTextNormalization) ?? config.applyTextNormalization, languageCode: normalizeOptionalString(overrides.languageCode) ?? config.languageCode, latencyTier, voiceSettings: resolveVoiceSettingsOverride(config.voiceSettings, overrides.voiceSettings), timeoutMs: req.timeoutMs }); return { audioStream: stream.audioStream, outputFormat, fileExtension: req.target === "voice-note" ? ".opus" : ".mp3", voiceCompatible: req.target === "voice-note", release: stream.release }; }, synthesizeTelephony: async (req) => { const config = readElevenLabsProviderConfig(req.providerConfig); const overrides = req.providerOverrides ?? {}; const apiKey = config.apiKey || resolveElevenLabsApiKeyWithProfileFallback() || process.env.XI_API_KEY; if (!apiKey) throw new Error("ElevenLabs API key missing"); const outputFormat = "pcm_22050"; return { audioBuffer: await elevenLabsTTS({ text: req.text, apiKey, baseUrl: config.baseUrl, voiceId: normalizeOptionalString(overrides.voiceId) ?? config.voiceId, modelId: normalizeOptionalString(overrides.modelId) ?? config.modelId, outputFormat, seed: normalizeElevenLabsSeed(overrides.seed) ?? config.seed, applyTextNormalization: normalizeOptionalString(overrides.applyTextNormalization) ?? config.applyTextNormalization, languageCode: normalizeOptionalString(overrides.languageCode) ?? config.languageCode, voiceSettings: resolveVoiceSettingsOverride(config.voiceSettings, overrides.voiceSettings), timeoutMs: req.timeoutMs }), outputFormat, sampleRate: 22050 }; } }; } //#endregion export { isValidVoiceId as n, buildElevenLabsSpeechProvider as t };