UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

255 lines (254 loc) 9.3 kB
import { c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js"; import { j as resolveTimerTimeoutMs } from "./number-coercion-CJQ8TR--.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 { i as assertOkOrThrowProviderError, n as asObject } from "./provider-http-errors-DqaqQLLZ.js"; import { i as transcodeAudioBufferToOpus } from "./media-services-DE3J1qM-.js"; import "./media-runtime-fgpmX6eL.js"; import "./number-runtime-DBLVDypr.js"; import "./speech-core-s8tZcO7c.js"; import "./secret-input-DVCzFGxN.js"; import "./ssrf-runtime-BOGN5pUi.js"; import "./provider-http-BXCBCi4E.js"; //#region extensions/xiaomi/speech-provider.ts const DEFAULT_XIAOMI_TTS_BASE_URL = "https://api.xiaomimimo.com/v1"; const DEFAULT_XIAOMI_TTS_MODEL = "mimo-v2.5-tts"; const DEFAULT_XIAOMI_TTS_VOICE = "mimo_default"; const DEFAULT_XIAOMI_TTS_FORMAT = "mp3"; const XIAOMI_TTS_VOICE_DESIGN_MODEL = "mimo-v2.5-tts-voicedesign"; const DEFAULT_XIAOMI_TTS_VOICE_DESIGN_STYLE = "Warm, natural, and friendly voice with clear pronunciation and conversational pacing."; const XIAOMI_TTS_MODELS = [ "mimo-v2.5-tts", "mimo-v2-tts", XIAOMI_TTS_VOICE_DESIGN_MODEL ]; const XIAOMI_TTS_VOICES = [ "mimo_default", "default_zh", "default_en", "Mia", "Chloe", "Milo", "Dean" ]; const XIAOMI_TTS_FORMATS = ["mp3", "wav"]; function normalizeXiaomiTtsBaseUrl(baseUrl) { return (baseUrl?.trim() || DEFAULT_XIAOMI_TTS_BASE_URL).replace(/\/+$/, ""); } function normalizeXiaomiTtsFormat(value) { const normalized = normalizeOptionalString(value)?.toLowerCase(); return XIAOMI_TTS_FORMATS.includes(normalized) ? normalized : void 0; } function resolveXiaomiTtsConfigRecord(rawConfig) { const providers = asObject(rawConfig.providers); return asObject(providers?.xiaomi) ?? asObject(providers?.mimo) ?? asObject(rawConfig.xiaomi); } function normalizeXiaomiTtsProviderConfig(rawConfig) { const raw = resolveXiaomiTtsConfigRecord(rawConfig); return { apiKey: normalizeResolvedSecretInputString({ value: raw?.apiKey, path: "messages.tts.providers.xiaomi.apiKey" }), baseUrl: normalizeXiaomiTtsBaseUrl(normalizeOptionalString(raw?.baseUrl) ?? normalizeOptionalString(process.env.XIAOMI_BASE_URL)), model: normalizeOptionalString(raw?.model) ?? normalizeOptionalString(raw?.modelId) ?? normalizeOptionalString(process.env.XIAOMI_TTS_MODEL) ?? DEFAULT_XIAOMI_TTS_MODEL, voice: normalizeOptionalString(raw?.speakerVoice) ?? normalizeOptionalString(raw?.speakerVoiceId) ?? normalizeOptionalString(raw?.voice) ?? normalizeOptionalString(raw?.voiceId) ?? normalizeOptionalString(process.env.XIAOMI_TTS_VOICE) ?? DEFAULT_XIAOMI_TTS_VOICE, format: normalizeXiaomiTtsFormat(raw?.format) ?? normalizeXiaomiTtsFormat(process.env.XIAOMI_TTS_FORMAT) ?? DEFAULT_XIAOMI_TTS_FORMAT, style: normalizeOptionalString(raw?.style) }; } function readXiaomiTtsProviderConfig(config) { const normalized = normalizeXiaomiTtsProviderConfig({}); return { apiKey: normalizeResolvedSecretInputString({ value: config.apiKey, path: "messages.tts.providers.xiaomi.apiKey" }) ?? normalized.apiKey, baseUrl: normalizeXiaomiTtsBaseUrl(normalizeOptionalString(config.baseUrl) ?? normalized.baseUrl), model: normalizeOptionalString(config.model) ?? normalizeOptionalString(config.modelId) ?? normalized.model, voice: normalizeOptionalString(config.speakerVoice) ?? normalizeOptionalString(config.speakerVoiceId) ?? normalizeOptionalString(config.voice) ?? normalizeOptionalString(config.voiceId) ?? normalized.voice, format: normalizeXiaomiTtsFormat(config.format) ?? normalized.format, style: normalizeOptionalString(config.style) ?? normalized.style }; } function readXiaomiTtsOverrides(overrides) { if (!overrides) return {}; return { model: normalizeOptionalString(overrides.model) ?? normalizeOptionalString(overrides.modelId), voice: normalizeOptionalString(overrides.speakerVoice) ?? normalizeOptionalString(overrides.speakerVoiceId) ?? normalizeOptionalString(overrides.voice) ?? normalizeOptionalString(overrides.voiceId), format: normalizeXiaomiTtsFormat(overrides.format), style: normalizeOptionalString(overrides.style) }; } function parseDirectiveToken(ctx) { switch (ctx.key) { case "voice": case "voiceid": case "voice_id": case "mimo_voice": case "xiaomi_voice": if (!ctx.policy.allowVoice) return { handled: true }; return { handled: true, overrides: { voice: ctx.value } }; case "model": case "mimo_model": case "xiaomi_model": if (!ctx.policy.allowModelId) return { handled: true }; return { handled: true, overrides: { model: ctx.value } }; case "style": case "mimo_style": case "xiaomi_style": if (!ctx.policy.allowVoiceSettings) return { handled: true }; return { handled: true, overrides: { style: ctx.value } }; case "format": case "responseformat": case "response_format": { if (!ctx.policy.allowVoiceSettings) return { handled: true }; const format = normalizeXiaomiTtsFormat(ctx.value); if (!format) return { handled: true, warnings: [`invalid Xiaomi TTS format "${ctx.value}"`] }; return { handled: true, overrides: { format } }; } default: return { handled: false }; } } function buildXiaomiTtsMessages(params) { const style = normalizeOptionalString(params.style); return [...style ? [{ role: "user", content: style }] : [], { role: "assistant", content: params.text }]; } function isXiaomiVoiceDesignModel(model) { return model === XIAOMI_TTS_VOICE_DESIGN_MODEL; } function resolveXiaomiVoiceDesignStyle(style) { return normalizeOptionalString(style) ?? DEFAULT_XIAOMI_TTS_VOICE_DESIGN_STYLE; } function buildXiaomiTtsAudio(params) { if (isXiaomiVoiceDesignModel(params.model)) return { format: params.format }; return { format: params.format, voice: params.voice }; } function decodeXiaomiAudioData(body) { const root = asObject(body); const audioData = normalizeOptionalString(asObject(asObject(asObject((Array.isArray(root?.choices) ? root.choices : [])[0])?.message)?.audio)?.data); if (!audioData) throw new Error("Xiaomi TTS API returned no audio data"); return Buffer.from(audioData, "base64"); } async function xiaomiTTS(params) { const { text, apiKey, baseUrl, model, voice, format, style, timeoutMs } = params; const requestTimeoutMs = resolveTimerTimeoutMs(timeoutMs, 1); const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), requestTimeoutMs); const resolvedStyle = isXiaomiVoiceDesignModel(model) ? resolveXiaomiVoiceDesignStyle(style) : style; try { const { response, release } = await fetchWithSsrFGuard({ url: `${baseUrl}/chat/completions`, init: { method: "POST", headers: { "api-key": apiKey, "Content-Type": "application/json" }, body: JSON.stringify({ model, messages: buildXiaomiTtsMessages({ text, style: resolvedStyle }), audio: buildXiaomiTtsAudio({ model, voice, format }) }), signal: controller.signal }, timeoutMs: requestTimeoutMs, policy: ssrfPolicyFromHttpBaseUrlAllowedHostname(baseUrl), auditContext: "xiaomi.tts" }); try { await assertOkOrThrowProviderError(response, "Xiaomi TTS API error"); return decodeXiaomiAudioData(await response.json()); } finally { await release(); } } finally { clearTimeout(timeout); } } function buildXiaomiSpeechProvider() { return { id: "xiaomi", label: "Xiaomi MiMo", aliases: ["mimo"], autoSelectOrder: 45, defaultModel: DEFAULT_XIAOMI_TTS_MODEL, models: XIAOMI_TTS_MODELS, voices: XIAOMI_TTS_VOICES, resolveConfig: ({ rawConfig }) => normalizeXiaomiTtsProviderConfig(rawConfig), parseDirectiveToken, listVoices: async () => XIAOMI_TTS_VOICES.map((voice) => ({ id: voice, name: voice })), isConfigured: ({ providerConfig }) => Boolean(readXiaomiTtsProviderConfig(providerConfig).apiKey || process.env.XIAOMI_API_KEY), synthesize: async (req) => { const config = readXiaomiTtsProviderConfig(req.providerConfig); const overrides = readXiaomiTtsOverrides(req.providerOverrides); const apiKey = config.apiKey || process.env.XIAOMI_API_KEY; if (!apiKey) throw new Error("Xiaomi API key missing"); const outputFormat = overrides.format ?? config.format; const audioBuffer = await xiaomiTTS({ text: req.text, apiKey, baseUrl: config.baseUrl, model: overrides.model ?? config.model, voice: overrides.voice ?? config.voice, format: outputFormat, style: overrides.style ?? config.style, timeoutMs: req.timeoutMs }); if (req.target === "voice-note") return { audioBuffer: await transcodeAudioBufferToOpus({ audioBuffer, inputExtension: outputFormat, tempPrefix: "tts-xiaomi-", timeoutMs: req.timeoutMs }), outputFormat: "opus", fileExtension: ".opus", voiceCompatible: true }; return { audioBuffer, outputFormat, fileExtension: `.${outputFormat}`, voiceCompatible: false }; } }; } //#endregion export { buildXiaomiSpeechProvider as t };