UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

208 lines (207 loc) 9.66 kB
import { c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js"; import { s as asFiniteNumber } from "./number-coercion-CJQ8TR--.js"; import { n as resolvePreferredOpenClawTmpDir } from "./tmp-openclaw-dir-DOKojISm.js"; import { t as tempWorkspace } from "./private-temp-workspace-MCwLg_M9.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 { g as isVoiceCompatibleAudio } from "./media-runtime-fgpmX6eL.js"; import "./temp-path-CeQpRzl5.js"; import { a as isDebugProxyGlobalFetchPatchInstalled, t as captureHttpExchange } from "./runtime-DrwkIUiy.js"; import "./proxy-capture-x_2F2qw1.js"; import "./ssrf-runtime-BOGN5pUi.js"; import "./provider-http-BXCBCi4E.js"; import "./speech-DeKvI3F1.js"; import { n as inferEdgeExtension, t as edgeTTS } from "./tts-DRoSrIVV.js"; import { readFileSync } from "node:fs"; import path from "node:path"; import { CHROMIUM_FULL_VERSION, TRUSTED_CLIENT_TOKEN, generateSecMsGecToken } from "node-edge-tts/dist/drm.js"; //#region extensions/microsoft/speech-provider.ts const DEFAULT_EDGE_VOICE = "en-US-MichelleNeural"; const DEFAULT_EDGE_LANG = "en-US"; const DEFAULT_EDGE_OUTPUT_FORMAT = "audio-24khz-48kbitrate-mono-mp3"; function normalizeMicrosoftProviderConfig(rawConfig) { const providers = asObject(rawConfig.providers); const rawEdge = asObject(rawConfig.edge); const rawMicrosoft = asObject(rawConfig.microsoft); const rawProviderMicrosoft = asObject(providers?.microsoft); const raw = { ...rawEdge, ...rawMicrosoft, ...rawProviderMicrosoft }; const outputFormat = normalizeOptionalString(raw.outputFormat); return { enabled: asBoolean(raw.enabled) ?? true, voice: normalizeOptionalString(raw.voice) ?? DEFAULT_EDGE_VOICE, lang: normalizeOptionalString(raw.lang) ?? DEFAULT_EDGE_LANG, outputFormat: outputFormat ?? DEFAULT_EDGE_OUTPUT_FORMAT, outputFormatConfigured: Boolean(outputFormat), pitch: normalizeOptionalString(raw.pitch), rate: normalizeOptionalString(raw.rate), volume: normalizeOptionalString(raw.volume), saveSubtitles: asBoolean(raw.saveSubtitles) ?? false, proxy: normalizeOptionalString(raw.proxy), timeoutMs: asFiniteNumber(raw.timeoutMs) }; } function readMicrosoftProviderConfig(config) { const defaults = normalizeMicrosoftProviderConfig({}); return { enabled: asBoolean(config.enabled) ?? defaults.enabled, voice: normalizeOptionalString(config.voice) ?? defaults.voice, lang: normalizeOptionalString(config.lang) ?? defaults.lang, outputFormat: normalizeOptionalString(config.outputFormat) ?? defaults.outputFormat, outputFormatConfigured: asBoolean(config.outputFormatConfigured) ?? defaults.outputFormatConfigured, pitch: normalizeOptionalString(config.pitch) ?? defaults.pitch, rate: normalizeOptionalString(config.rate) ?? defaults.rate, volume: normalizeOptionalString(config.volume) ?? defaults.volume, saveSubtitles: asBoolean(config.saveSubtitles) ?? defaults.saveSubtitles, proxy: normalizeOptionalString(config.proxy) ?? defaults.proxy, timeoutMs: asFiniteNumber(config.timeoutMs) ?? defaults.timeoutMs }; } function buildMicrosoftVoiceHeaders() { const major = CHROMIUM_FULL_VERSION.split(".")[0] || "0"; return { Authority: "speech.platform.bing.com", Origin: "chrome-extension://jdiccldimpdaibmpdkjnbmckianbfold", Accept: "*/*", "User-Agent": `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${major}.0.0.0 Safari/537.36 Edg/${major}.0.0.0`, "Sec-MS-GEC": generateSecMsGecToken(), "Sec-MS-GEC-Version": `1-${CHROMIUM_FULL_VERSION}` }; } function formatMicrosoftVoiceDescription(entry) { const personalities = entry.VoiceTag?.VoicePersonalities?.filter(Boolean) ?? []; return personalities.length > 0 ? personalities.join(", ") : void 0; } function isCjkDominant(text) { const stripped = text.replace(/\s+/g, ""); if (stripped.length === 0) return false; let cjkCount = 0; for (const ch of stripped) { const code = ch.codePointAt(0) ?? 0; if (code >= 19968 && code <= 40959 || code >= 13312 && code <= 19903 || code >= 12288 && code <= 12351 || code >= 65280 && code <= 65519) cjkCount += 1; } return cjkCount / stripped.length > .3; } const DEFAULT_CHINESE_EDGE_VOICE = "zh-CN-XiaoxiaoNeural"; const DEFAULT_CHINESE_EDGE_LANG = "zh-CN"; async function listMicrosoftVoices() { const url = `https://speech.platform.bing.com/consumer/speech/synthesize/readaloud/voices/list?trustedclienttoken=${TRUSTED_CLIENT_TOKEN}`; const headers = buildMicrosoftVoiceHeaders(); const { response, release } = await fetchWithSsrFGuard({ url, init: { headers }, policy: ssrfPolicyFromHttpBaseUrlAllowedHostname("https://speech.platform.bing.com"), auditContext: "microsoft.speech.voices" }); try { if (!isDebugProxyGlobalFetchPatchInstalled()) captureHttpExchange({ url, method: "GET", requestHeaders: headers, response, transport: "http", meta: { provider: "microsoft", capability: "speech-voices" } }); await assertOkOrThrowProviderError(response, "Microsoft voices API error"); const voices = await response.json(); return Array.isArray(voices) ? voices.map((voice) => ({ id: voice.ShortName?.trim() ?? "", name: normalizeOptionalString(voice.FriendlyName) ?? normalizeOptionalString(voice.ShortName), category: voice.VoiceTag?.ContentCategories?.find((value) => value.trim().length > 0), description: formatMicrosoftVoiceDescription(voice), locale: normalizeOptionalString(voice.Locale), gender: normalizeOptionalString(voice.Gender), personalities: voice.VoiceTag?.VoicePersonalities?.filter((value) => value.trim().length > 0) })).filter((voice) => voice.id.length > 0) : []; } finally { await release(); } } function buildMicrosoftSpeechProvider() { return { id: "microsoft", label: "Microsoft", aliases: ["edge"], autoSelectOrder: 30, resolveConfig: ({ rawConfig }) => normalizeMicrosoftProviderConfig(rawConfig), resolveTalkConfig: ({ baseTtsConfig, talkProviderConfig }) => { return { ...normalizeMicrosoftProviderConfig(baseTtsConfig), enabled: true, ...normalizeOptionalString(talkProviderConfig.voiceId) == null ? {} : { voice: normalizeOptionalString(talkProviderConfig.voiceId) }, ...normalizeOptionalString(talkProviderConfig.languageCode) == null ? {} : { lang: normalizeOptionalString(talkProviderConfig.languageCode) }, ...normalizeOptionalString(talkProviderConfig.outputFormat) == null ? {} : { outputFormat: normalizeOptionalString(talkProviderConfig.outputFormat) }, ...normalizeOptionalString(talkProviderConfig.pitch) == null ? {} : { pitch: normalizeOptionalString(talkProviderConfig.pitch) }, ...normalizeOptionalString(talkProviderConfig.rate) == null ? {} : { rate: normalizeOptionalString(talkProviderConfig.rate) }, ...normalizeOptionalString(talkProviderConfig.volume) == null ? {} : { volume: normalizeOptionalString(talkProviderConfig.volume) }, ...normalizeOptionalString(talkProviderConfig.proxy) == null ? {} : { proxy: normalizeOptionalString(talkProviderConfig.proxy) }, ...asFiniteNumber(talkProviderConfig.timeoutMs) == null ? {} : { timeoutMs: asFiniteNumber(talkProviderConfig.timeoutMs) } }; }, resolveTalkOverrides: ({ params }) => ({ ...normalizeOptionalString(params.voiceId) == null ? {} : { voice: normalizeOptionalString(params.voiceId) }, ...normalizeOptionalString(params.outputFormat) == null ? {} : { outputFormat: normalizeOptionalString(params.outputFormat) } }), listVoices: async () => await listMicrosoftVoices(), isConfigured: ({ providerConfig }) => readMicrosoftProviderConfig(providerConfig).enabled, synthesize: async (req) => { const config = readMicrosoftProviderConfig(req.providerConfig); const temp = await tempWorkspace({ rootDir: resolvePreferredOpenClawTmpDir(), prefix: "tts-microsoft-" }); const tempDir = temp.dir; const overrideVoice = normalizeOptionalString(req.providerOverrides?.voice); let voice = overrideVoice ?? config.voice; let lang = config.lang; let outputFormat = normalizeOptionalString(req.providerOverrides?.outputFormat) ?? config.outputFormat; const fallbackOutputFormat = outputFormat !== DEFAULT_EDGE_OUTPUT_FORMAT ? DEFAULT_EDGE_OUTPUT_FORMAT : void 0; if (!overrideVoice && voice === DEFAULT_EDGE_VOICE && isCjkDominant(req.text)) { voice = DEFAULT_CHINESE_EDGE_VOICE; lang = DEFAULT_CHINESE_EDGE_LANG; } try { const runEdge = async (format) => { const fileExtension = inferEdgeExtension(format); const outputPath = path.join(tempDir, `speech${fileExtension}`); await edgeTTS({ text: req.text, outputPath, config: { ...config, voice, lang, outputFormat: format }, timeoutMs: req.timeoutMs }); return { audioBuffer: readFileSync(outputPath), outputFormat: format, fileExtension, voiceCompatible: isVoiceCompatibleAudio({ fileName: outputPath }) }; }; try { return await runEdge(outputFormat); } catch (error) { if (!fallbackOutputFormat || fallbackOutputFormat === outputFormat) throw error; outputFormat = fallbackOutputFormat; return await runEdge(outputFormat); } } finally { await temp.cleanup(); } } }; } //#endregion export { isCjkDominant as n, listMicrosoftVoices as r, buildMicrosoftSpeechProvider as t };