mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-21 06:02:13 +00:00
169 lines
4.6 KiB
TypeScript
169 lines
4.6 KiB
TypeScript
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import { withEnv } from "../test-utils/env.js";
|
|
import type { ResolvedTtsConfig } from "./tts-config.js";
|
|
import {
|
|
resolveExtensionHostTtsProvider,
|
|
resolveExtensionHostTtsRequestSetup,
|
|
} from "./tts-runtime-setup.js";
|
|
|
|
vi.mock("./runtime-backend-catalog.js", () => ({
|
|
resolveExtensionHostTtsRuntimeBackendOrder: vi.fn((provider: string) =>
|
|
[provider, "openai", "elevenlabs", "edge"].filter(
|
|
(candidate, index, items) => items.indexOf(candidate) === index,
|
|
),
|
|
),
|
|
listExtensionHostTtsRuntimeBackendCatalogEntries: vi.fn(() => [
|
|
{
|
|
id: "capability.runtime-backend:tts:openai",
|
|
family: "capability.runtime-backend",
|
|
subsystemId: "tts",
|
|
backendId: "openai",
|
|
source: "builtin",
|
|
defaultRank: 0,
|
|
selectorKeys: ["openai"],
|
|
capabilities: ["tts.synthesis", "tts.telephony"],
|
|
},
|
|
{
|
|
id: "capability.runtime-backend:tts:elevenlabs",
|
|
family: "capability.runtime-backend",
|
|
subsystemId: "tts",
|
|
backendId: "elevenlabs",
|
|
source: "builtin",
|
|
defaultRank: 1,
|
|
selectorKeys: ["elevenlabs"],
|
|
capabilities: ["tts.synthesis", "tts.telephony"],
|
|
},
|
|
{
|
|
id: "capability.runtime-backend:tts:edge",
|
|
family: "capability.runtime-backend",
|
|
subsystemId: "tts",
|
|
backendId: "edge",
|
|
source: "builtin",
|
|
defaultRank: 2,
|
|
selectorKeys: ["edge"],
|
|
capabilities: ["tts.synthesis"],
|
|
},
|
|
]),
|
|
}));
|
|
|
|
const tempDirs: string[] = [];
|
|
|
|
function createPrefsPath(contents: object): string {
|
|
const tempDir = mkdtempSync(path.join(os.tmpdir(), "openclaw-tts-setup-"));
|
|
tempDirs.push(tempDir);
|
|
const prefsPath = path.join(tempDir, "tts.json");
|
|
writeFileSync(prefsPath, JSON.stringify(contents), "utf8");
|
|
return prefsPath;
|
|
}
|
|
|
|
function createResolvedConfig(overrides?: Partial<ResolvedTtsConfig>): ResolvedTtsConfig {
|
|
return {
|
|
auto: "off",
|
|
mode: "final",
|
|
provider: "edge",
|
|
providerSource: "default",
|
|
modelOverrides: {
|
|
enabled: true,
|
|
allowText: true,
|
|
allowProvider: false,
|
|
allowVoice: true,
|
|
allowModelId: true,
|
|
allowVoiceSettings: true,
|
|
allowNormalization: true,
|
|
allowSeed: true,
|
|
},
|
|
elevenlabs: {
|
|
baseUrl: "https://api.elevenlabs.io",
|
|
voiceId: "voice-id",
|
|
modelId: "eleven_multilingual_v2",
|
|
voiceSettings: {
|
|
stability: 0.5,
|
|
similarityBoost: 0.75,
|
|
style: 0,
|
|
useSpeakerBoost: true,
|
|
speed: 1,
|
|
},
|
|
},
|
|
openai: {
|
|
baseUrl: "https://api.openai.com/v1",
|
|
model: "gpt-4o-mini-tts",
|
|
voice: "alloy",
|
|
},
|
|
edge: {
|
|
enabled: true,
|
|
voice: "en-US-MichelleNeural",
|
|
lang: "en-US",
|
|
outputFormat: "audio-24khz-48kbitrate-mono-mp3",
|
|
outputFormatConfigured: false,
|
|
saveSubtitles: false,
|
|
},
|
|
maxTextLength: 4096,
|
|
timeoutMs: 30_000,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
afterEach(() => {
|
|
for (const tempDir of tempDirs.splice(0)) {
|
|
rmSync(tempDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
describe("tts-runtime-setup", () => {
|
|
it("prefers the stored provider over config and environment", () => {
|
|
const prefsPath = createPrefsPath({ tts: { provider: "elevenlabs" } });
|
|
const config = createResolvedConfig({
|
|
provider: "openai",
|
|
providerSource: "config",
|
|
openai: {
|
|
baseUrl: "https://api.openai.com/v1",
|
|
model: "gpt-4o-mini-tts",
|
|
voice: "alloy",
|
|
apiKey: "config-openai-key",
|
|
},
|
|
});
|
|
|
|
withEnv({ OPENAI_API_KEY: "env-openai-key", ELEVENLABS_API_KEY: undefined }, () => {
|
|
expect(resolveExtensionHostTtsProvider(config, prefsPath)).toBe("elevenlabs");
|
|
});
|
|
});
|
|
|
|
it("returns a validation error when text exceeds the configured hard limit", () => {
|
|
const config = createResolvedConfig({ maxTextLength: 5 });
|
|
const prefsPath = createPrefsPath({});
|
|
|
|
expect(
|
|
resolveExtensionHostTtsRequestSetup({
|
|
text: "too-long",
|
|
config,
|
|
prefsPath,
|
|
}),
|
|
).toEqual({
|
|
error: "Text too long (8 chars, max 5)",
|
|
});
|
|
});
|
|
|
|
it("uses the override provider to build the host-owned configured fallback order", () => {
|
|
const config = createResolvedConfig({
|
|
provider: "edge",
|
|
providerSource: "config",
|
|
});
|
|
const prefsPath = createPrefsPath({});
|
|
|
|
expect(
|
|
resolveExtensionHostTtsRequestSetup({
|
|
text: "hello world",
|
|
config,
|
|
prefsPath,
|
|
providerOverride: "elevenlabs",
|
|
}),
|
|
).toEqual({
|
|
config,
|
|
providers: ["elevenlabs", "edge"],
|
|
});
|
|
});
|
|
});
|