mirror of https://github.com/openclaw/openclaw.git
TTS: add backend fallback policy
This commit is contained in:
parent
6f8408b813
commit
1b87cdeca1
|
|
@ -43,3 +43,22 @@ export function resolveExtensionHostRuntimeBackendOrderByArbitration(params: {
|
|||
...ordered.filter((backendId) => backendId !== params.preferredBackendId),
|
||||
];
|
||||
}
|
||||
|
||||
export function resolveExtensionHostRuntimeBackendFallbackChainByArbitration(params: {
|
||||
entries: readonly ExtensionHostRuntimeBackendCatalogEntry[];
|
||||
subsystemId: ExtensionHostRuntimeBackendSubsystemId;
|
||||
preferredBackendId: string;
|
||||
include?: ExtensionHostRuntimeBackendArbitrationPredicate;
|
||||
}): readonly string[] {
|
||||
return resolveExtensionHostRuntimeBackendOrderByArbitration(params);
|
||||
}
|
||||
|
||||
export function resolveExtensionHostDefaultRuntimeBackendIdByArbitration(params: {
|
||||
entries: readonly ExtensionHostRuntimeBackendCatalogEntry[];
|
||||
subsystemId: ExtensionHostRuntimeBackendSubsystemId;
|
||||
include?: ExtensionHostRuntimeBackendArbitrationPredicate;
|
||||
fallbackBackendId?: string;
|
||||
}): string | undefined {
|
||||
const ordered = listExtensionHostRuntimeBackendIdsByArbitration(params);
|
||||
return ordered[0] ?? params.fallbackBackendId;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
resolveExtensionHostDefaultTtsProvider,
|
||||
resolveExtensionHostTtsFallbackProviders,
|
||||
} from "./tts-runtime-policy.js";
|
||||
|
||||
vi.mock("./runtime-backend-catalog.js", () => ({
|
||||
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"],
|
||||
},
|
||||
]),
|
||||
}));
|
||||
|
||||
vi.mock("./tts-runtime-registry.js", () => ({
|
||||
isExtensionHostTtsProviderConfigured: vi.fn(
|
||||
(
|
||||
config: {
|
||||
configured?: string[];
|
||||
},
|
||||
provider: string,
|
||||
) => config.configured?.includes(provider) ?? false,
|
||||
),
|
||||
}));
|
||||
|
||||
describe("tts-runtime-policy", () => {
|
||||
it("selects the highest-ranked configured provider by default", () => {
|
||||
expect(
|
||||
resolveExtensionHostDefaultTtsProvider({
|
||||
configured: ["elevenlabs", "edge"],
|
||||
} as never),
|
||||
).toBe("elevenlabs");
|
||||
});
|
||||
|
||||
it("falls back to edge when no configured provider is available", () => {
|
||||
expect(resolveExtensionHostDefaultTtsProvider({ configured: [] } as never)).toBe("edge");
|
||||
});
|
||||
|
||||
it("keeps the preferred provider first while filtering fallback providers by configuration", () => {
|
||||
expect(
|
||||
resolveExtensionHostTtsFallbackProviders({
|
||||
config: { configured: ["openai", "edge"] } as never,
|
||||
preferredProvider: "elevenlabs",
|
||||
}),
|
||||
).toEqual(["elevenlabs", "openai", "edge"]);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
import type { TtsProvider } from "../config/types.tts.js";
|
||||
import {
|
||||
resolveExtensionHostDefaultRuntimeBackendIdByArbitration,
|
||||
resolveExtensionHostRuntimeBackendFallbackChainByArbitration,
|
||||
} from "./runtime-backend-arbitration.js";
|
||||
import {
|
||||
listExtensionHostTtsRuntimeBackendCatalogEntries,
|
||||
type ExtensionHostRuntimeBackendCatalogEntry,
|
||||
} from "./runtime-backend-catalog.js";
|
||||
import type { ResolvedTtsConfig } from "./tts-config.js";
|
||||
import { isExtensionHostTtsProviderConfigured } from "./tts-runtime-registry.js";
|
||||
|
||||
function isConfiguredTtsRuntimeBackend(
|
||||
config: ResolvedTtsConfig,
|
||||
entry: ExtensionHostRuntimeBackendCatalogEntry,
|
||||
): boolean {
|
||||
return isExtensionHostTtsProviderConfigured(config, entry.backendId as TtsProvider);
|
||||
}
|
||||
|
||||
export function resolveExtensionHostDefaultTtsProvider(config: ResolvedTtsConfig): TtsProvider {
|
||||
return (resolveExtensionHostDefaultRuntimeBackendIdByArbitration({
|
||||
entries: listExtensionHostTtsRuntimeBackendCatalogEntries(),
|
||||
subsystemId: "tts",
|
||||
include: (entry) => isConfiguredTtsRuntimeBackend(config, entry),
|
||||
fallbackBackendId: "edge",
|
||||
}) ?? "edge") as TtsProvider;
|
||||
}
|
||||
|
||||
export function resolveExtensionHostTtsFallbackProviders(params: {
|
||||
config: ResolvedTtsConfig;
|
||||
preferredProvider: TtsProvider;
|
||||
}): readonly TtsProvider[] {
|
||||
return resolveExtensionHostRuntimeBackendFallbackChainByArbitration({
|
||||
entries: listExtensionHostTtsRuntimeBackendCatalogEntries(),
|
||||
subsystemId: "tts",
|
||||
preferredBackendId: params.preferredProvider,
|
||||
include: (entry) => isConfiguredTtsRuntimeBackend(params.config, entry),
|
||||
}).map((backendId) => backendId as TtsProvider);
|
||||
}
|
||||
|
|
@ -15,6 +15,38 @@ vi.mock("./runtime-backend-catalog.js", () => ({
|
|||
(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[] = [];
|
||||
|
|
@ -114,7 +146,7 @@ describe("tts-runtime-setup", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("uses the override provider to build the host-owned fallback order", () => {
|
||||
it("uses the override provider to build the host-owned configured fallback order", () => {
|
||||
const config = createResolvedConfig({
|
||||
provider: "edge",
|
||||
providerSource: "config",
|
||||
|
|
@ -130,7 +162,7 @@ describe("tts-runtime-setup", () => {
|
|||
}),
|
||||
).toEqual({
|
||||
config,
|
||||
providers: ["elevenlabs", "openai", "edge"],
|
||||
providers: ["elevenlabs", "edge"],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { existsSync, readFileSync } from "node:fs";
|
||||
import type { TtsProvider } from "../config/types.tts.js";
|
||||
import { resolveExtensionHostTtsRuntimeBackendOrder } from "./runtime-backend-catalog.js";
|
||||
import type { ResolvedTtsConfig } from "./tts-config.js";
|
||||
import { resolveExtensionHostTtsApiKey } from "./tts-runtime-registry.js";
|
||||
import {
|
||||
resolveExtensionHostDefaultTtsProvider,
|
||||
resolveExtensionHostTtsFallbackProviders,
|
||||
} from "./tts-runtime-policy.js";
|
||||
|
||||
type TtsUserPrefs = {
|
||||
tts?: {
|
||||
|
|
@ -35,13 +37,7 @@ export function resolveExtensionHostTtsProvider(
|
|||
return config.provider;
|
||||
}
|
||||
|
||||
if (resolveExtensionHostTtsApiKey(config, "openai")) {
|
||||
return "openai";
|
||||
}
|
||||
if (resolveExtensionHostTtsApiKey(config, "elevenlabs")) {
|
||||
return "elevenlabs";
|
||||
}
|
||||
return "edge";
|
||||
return resolveExtensionHostDefaultTtsProvider(config);
|
||||
}
|
||||
|
||||
export function resolveExtensionHostTtsRequestSetup(params: {
|
||||
|
|
@ -67,6 +63,11 @@ export function resolveExtensionHostTtsRequestSetup(params: {
|
|||
params.providerOverride ?? resolveExtensionHostTtsProvider(params.config, params.prefsPath);
|
||||
return {
|
||||
config: params.config,
|
||||
providers: [...resolveExtensionHostTtsRuntimeBackendOrder(provider)],
|
||||
providers: [
|
||||
...resolveExtensionHostTtsFallbackProviders({
|
||||
config: params.config,
|
||||
preferredProvider: provider,
|
||||
}),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import type { TtsProvider } from "../config/types.tts.js";
|
||||
import { resolveExtensionHostTtsRuntimeBackendOrder } from "./runtime-backend-catalog.js";
|
||||
import type { ResolvedTtsConfig } from "./tts-config.js";
|
||||
import {
|
||||
getExtensionHostTtsMaxLength,
|
||||
|
|
@ -7,6 +6,7 @@ import {
|
|||
isExtensionHostTtsSummarizationEnabled,
|
||||
resolveExtensionHostTtsAutoMode,
|
||||
} from "./tts-preferences.js";
|
||||
import { resolveExtensionHostTtsFallbackProviders } from "./tts-runtime-policy.js";
|
||||
import {
|
||||
isExtensionHostTtsProviderConfigured,
|
||||
resolveExtensionHostTtsApiKey,
|
||||
|
|
@ -57,9 +57,10 @@ export function resolveExtensionHostTtsStatusSnapshot(params: {
|
|||
}): ExtensionHostTtsStatusSnapshot {
|
||||
const { config, prefsPath } = params;
|
||||
const provider = resolveExtensionHostTtsProvider(config, prefsPath);
|
||||
const fallbackProviders = resolveExtensionHostTtsRuntimeBackendOrder(provider)
|
||||
.slice(1)
|
||||
.filter((candidate) => isExtensionHostTtsProviderConfigured(config, candidate));
|
||||
const fallbackProviders = resolveExtensionHostTtsFallbackProviders({
|
||||
config,
|
||||
preferredProvider: provider,
|
||||
}).slice(1);
|
||||
return {
|
||||
enabled: isExtensionHostTtsEnabled(config, prefsPath),
|
||||
auto: resolveExtensionHostTtsAutoMode({ config, prefsPath }),
|
||||
|
|
|
|||
Loading…
Reference in New Issue