From 29069bd250b91af561de60016efb4563386d940e Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 26 Mar 2026 21:49:20 +0000 Subject: [PATCH] refactor: share speech normalization helpers --- extensions/elevenlabs/tts.ts | 48 +++++-------------------------- src/plugin-sdk/channel-actions.ts | 5 ++-- src/plugin-sdk/speech-core.ts | 8 +++++- src/tts/tts-core.ts | 8 +++--- 4 files changed, 20 insertions(+), 49 deletions(-) diff --git a/extensions/elevenlabs/tts.ts b/extensions/elevenlabs/tts.ts index bebf1df9060..dff127efbbc 100644 --- a/extensions/elevenlabs/tts.ts +++ b/extensions/elevenlabs/tts.ts @@ -1,3 +1,10 @@ +import { + normalizeApplyTextNormalization, + normalizeLanguageCode, + normalizeSeed, + requireInRange, +} from "openclaw/plugin-sdk/speech-core"; + const DEFAULT_ELEVENLABS_BASE_URL = "https://api.elevenlabs.io"; function isValidVoiceId(voiceId: string): boolean { @@ -12,47 +19,6 @@ function normalizeElevenLabsBaseUrl(baseUrl?: string): string { return trimmed.replace(/\/+$/, ""); } -function normalizeLanguageCode(code?: string): string | undefined { - const trimmed = code?.trim(); - if (!trimmed) { - return undefined; - } - const normalized = trimmed.toLowerCase(); - if (!/^[a-z]{2}$/.test(normalized)) { - throw new Error("languageCode must be a 2-letter ISO 639-1 code (e.g. en, de, fr)"); - } - return normalized; -} - -function normalizeApplyTextNormalization(mode?: string): "auto" | "on" | "off" | undefined { - const trimmed = mode?.trim(); - if (!trimmed) { - return undefined; - } - const normalized = trimmed.toLowerCase(); - if (normalized === "auto" || normalized === "on" || normalized === "off") { - return normalized; - } - throw new Error("applyTextNormalization must be one of: auto, on, off"); -} - -function normalizeSeed(seed?: number): number | undefined { - if (seed == null) { - return undefined; - } - const next = Math.floor(seed); - if (!Number.isFinite(next) || next < 0 || next > 4_294_967_295) { - throw new Error("seed must be between 0 and 4294967295"); - } - return next; -} - -function requireInRange(value: number, min: number, max: number, label: string): void { - if (!Number.isFinite(value) || value < min || value > max) { - throw new Error(`${label} must be between ${min} and ${max}`); - } -} - function assertElevenLabsVoiceSettings(settings: { stability: number; similarityBoost: number; diff --git a/src/plugin-sdk/channel-actions.ts b/src/plugin-sdk/channel-actions.ts index 39d5b94ddaf..4153fdcb63e 100644 --- a/src/plugin-sdk/channel-actions.ts +++ b/src/plugin-sdk/channel-actions.ts @@ -6,8 +6,7 @@ export { resolveReactionMessageId } from "../channels/plugins/actions/reaction-m export { optionalStringEnum, stringEnum } from "../agents/schema/typebox.js"; import { Type } from "@sinclair/typebox"; import type { TSchema } from "@sinclair/typebox"; -import { stringEnum } from "../agents/schema/typebox.js"; -export { optionalStringEnum, stringEnum } from "../agents/schema/typebox.js"; +import { stringEnum as createStringEnum } from "../agents/schema/typebox.js"; /** Schema helper for channels that expose button rows on the shared `message` tool. */ export function createMessageToolButtonsSchema(): TSchema { @@ -17,7 +16,7 @@ export function createMessageToolButtonsSchema(): TSchema { Type.Object({ text: Type.String(), callback_data: Type.String(), - style: Type.Optional(stringEnum(["danger", "success", "primary"])), + style: Type.Optional(createStringEnum(["danger", "success", "primary"])), }), ), { diff --git a/src/plugin-sdk/speech-core.ts b/src/plugin-sdk/speech-core.ts index 75f9100fbe7..46274a62931 100644 --- a/src/plugin-sdk/speech-core.ts +++ b/src/plugin-sdk/speech-core.ts @@ -3,4 +3,10 @@ export type { SpeechProviderPlugin } from "../plugins/types.js"; export type { SpeechVoiceOption } from "../tts/provider-types.js"; -export { parseTtsDirectives } from "../tts/tts-core.js"; +export { + normalizeApplyTextNormalization, + normalizeLanguageCode, + normalizeSeed, + parseTtsDirectives, + requireInRange, +} from "../tts/tts-core.js"; diff --git a/src/tts/tts-core.ts b/src/tts/tts-core.ts index 809c18c2d78..8f7cc4d8170 100644 --- a/src/tts/tts-core.ts +++ b/src/tts/tts-core.ts @@ -37,13 +37,13 @@ function trimToUndefined(value?: string): string | undefined { return trimmed ? trimmed : undefined; } -function requireInRange(value: number, min: number, max: number, label: string): void { +export function requireInRange(value: number, min: number, max: number, label: string): void { if (!Number.isFinite(value) || value < min || value > max) { throw new Error(`${label} must be between ${min} and ${max}`); } } -function normalizeLanguageCode(code?: string): string | undefined { +export function normalizeLanguageCode(code?: string): string | undefined { const trimmed = code?.trim(); if (!trimmed) { return undefined; @@ -55,7 +55,7 @@ function normalizeLanguageCode(code?: string): string | undefined { return normalized; } -function normalizeApplyTextNormalization(mode?: string): "auto" | "on" | "off" | undefined { +export function normalizeApplyTextNormalization(mode?: string): "auto" | "on" | "off" | undefined { const trimmed = mode?.trim(); if (!trimmed) { return undefined; @@ -67,7 +67,7 @@ function normalizeApplyTextNormalization(mode?: string): "auto" | "on" | "off" | throw new Error("applyTextNormalization must be one of: auto, on, off"); } -function normalizeSeed(seed?: number): number | undefined { +export function normalizeSeed(seed?: number): number | undefined { if (seed == null) { return undefined; }