refactor(tts): consolidate speech provider ordering (#129508)

Amp-Thread-ID: https://ampcode.com/threads/T-01a037b7-785b-75eb-8426-ad67094e61d4

Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Peter Steinberger
2026-08-25 14:16:10 -07:00
committed by GitHub
parent 43ffe41a4b
commit 73294a85f2
4 changed files with 21 additions and 23 deletions
+3 -13
View File
@@ -4,6 +4,7 @@ import type { OpenClawConfig } from "../config/types.js";
import type { AssistantDeliveryTtsFacts } from "../llm/types.js";
import type { SpeechProviderPlugin } from "../plugins/types.js";
import { extractTtsDirectiveFacts } from "./directive-facts.js";
import { compareSpeechProviderOrder } from "./provider-registry-core.js";
import { listSpeechProviders } from "./provider-registry.js";
import type {
SpeechModelOverridePolicy,
@@ -31,20 +32,9 @@ type TtsDirectiveTextStreamCleaner = {
hasBufferedDirectiveText: () => boolean;
};
function buildProviderOrder(left: SpeechProviderPlugin, right: SpeechProviderPlugin): number {
const leftOrder = left.autoSelectOrder ?? Number.MAX_SAFE_INTEGER;
const rightOrder = right.autoSelectOrder ?? Number.MAX_SAFE_INTEGER;
if (leftOrder !== rightOrder) {
return leftOrder - rightOrder;
}
return left.id.localeCompare(right.id);
}
function resolveDirectiveProviders(options?: ParseTtsDirectiveOptions): SpeechProviderPlugin[] {
if (options?.providers) {
return [...options.providers].toSorted(buildProviderOrder);
}
return listSpeechProviders(options?.cfg).toSorted(buildProviderOrder);
const providers = options?.providers ?? listSpeechProviders(options?.cfg);
return providers.toSorted(compareSpeechProviderOrder);
}
function resolveDirectiveProviderConfig(
+13
View File
@@ -20,6 +20,19 @@ export function normalizeSpeechProviderId(
return normalizeCapabilityProviderId(providerId);
}
/** Order speech providers by priority and provider ID for deterministic equal-priority fallback. */
export function compareSpeechProviderOrder(
left: SpeechProviderPlugin,
right: SpeechProviderPlugin,
): number {
const leftOrder = left.autoSelectOrder ?? Number.MAX_SAFE_INTEGER;
const rightOrder = right.autoSelectOrder ?? Number.MAX_SAFE_INTEGER;
if (leftOrder !== rightOrder) {
return leftOrder - rightOrder;
}
return left.id.localeCompare(right.id);
}
/** Create a registry facade with canonical listing, alias lookup, and ID canonicalization. */
export function createSpeechProviderRegistry(resolver: SpeechProviderRegistryResolver) {
const buildResolvedProviderMaps = (cfg?: OpenClawConfig) =>
+3 -2
View File
@@ -96,10 +96,11 @@ describe("speech provider registry", () => {
expect(registry.canonicalizeSpeechProviderId("edge")).toBe("microsoft");
});
it("resolves fallback order and aliases from a supplied provider inventory", () => {
it("resolves deterministic fallback order and aliases from a supplied provider inventory", () => {
const inventory = [
{ ...createSpeechProvider("openai", ["oai"]), autoSelectOrder: 5 },
{ ...createSpeechProvider("google"), autoSelectOrder: 1 },
{ ...createSpeechProvider("azure"), autoSelectOrder: 1 },
{ ...createSpeechProvider("elevenlabs"), autoSelectOrder: 3 },
];
@@ -109,7 +110,7 @@ describe("speech provider registry", () => {
undefined,
inventory,
),
).toEqual(["openai", "google", "elevenlabs"]);
).toEqual(["openai", "azure", "google", "elevenlabs"]);
});
it("selects the first configured provider entirely from prepared facts", () => {
+2 -8
View File
@@ -10,6 +10,7 @@ import type {
TtsProvider,
} from "../config/types.js";
import type { SpeechProviderPlugin } from "../plugins/types.js";
import { compareSpeechProviderOrder } from "./provider-registry-core.js";
import {
canonicalizeSpeechProviderId,
getSpeechProvider,
@@ -64,14 +65,7 @@ function sortSpeechProvidersForAutoSelection(
cfg?: OpenClawConfig,
providers?: readonly SpeechProviderPlugin[],
) {
return [...(providers ?? listSpeechProviders(cfg))].toSorted((left, right) => {
const leftOrder = left.autoSelectOrder ?? Number.MAX_SAFE_INTEGER;
const rightOrder = right.autoSelectOrder ?? Number.MAX_SAFE_INTEGER;
if (leftOrder !== rightOrder) {
return leftOrder - rightOrder;
}
return left.id.localeCompare(right.id);
});
return [...(providers ?? listSpeechProviders(cfg))].toSorted(compareSpeechProviderOrder);
}
function canonicalizeSpeechProviderIdFromInventory(