mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(microsoft): keep valid voices from malformed catalogs (#110784)
This commit is contained in:
@@ -105,6 +105,52 @@ describe("listMicrosoftVoices", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("returns an empty catalog for a malformed top-level payload", async () => {
|
||||
globalThis.fetch = vi
|
||||
.fn()
|
||||
.mockResolvedValue(
|
||||
new Response("null", { status: 200 }),
|
||||
) as unknown as typeof globalThis.fetch;
|
||||
|
||||
await expect(listVoicesThroughProvider()).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it("skips malformed rows without discarding valid voices", async () => {
|
||||
globalThis.fetch = vi.fn().mockResolvedValue(
|
||||
new Response(
|
||||
JSON.stringify([
|
||||
null,
|
||||
"unexpected",
|
||||
[],
|
||||
{ ShortName: 42 },
|
||||
{
|
||||
ShortName: "en-US-AvaNeural",
|
||||
FriendlyName: "Microsoft Ava Online (Natural) - English (United States)",
|
||||
Locale: "en-US",
|
||||
Gender: "Female",
|
||||
VoiceTag: {
|
||||
ContentCategories: [null, "General"],
|
||||
VoicePersonalities: [false, "Friendly", "Positive"],
|
||||
},
|
||||
},
|
||||
]),
|
||||
{ status: 200 },
|
||||
),
|
||||
) as unknown as typeof globalThis.fetch;
|
||||
|
||||
await expect(listVoicesThroughProvider()).resolves.toEqual([
|
||||
{
|
||||
id: "en-US-AvaNeural",
|
||||
name: "Microsoft Ava Online (Natural) - English (United States)",
|
||||
category: "General",
|
||||
description: "Friendly, Positive",
|
||||
locale: "en-US",
|
||||
gender: "Female",
|
||||
personalities: ["Friendly", "Positive"],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("throws on Microsoft voice list failures", async () => {
|
||||
globalThis.fetch = vi
|
||||
.fn()
|
||||
|
||||
@@ -47,17 +47,6 @@ type MicrosoftProviderConfig = {
|
||||
timeoutMs?: number;
|
||||
};
|
||||
|
||||
type MicrosoftVoiceListEntry = {
|
||||
ShortName?: string;
|
||||
FriendlyName?: string;
|
||||
Locale?: string;
|
||||
Gender?: string;
|
||||
VoiceTag?: {
|
||||
ContentCategories?: string[];
|
||||
VoicePersonalities?: string[];
|
||||
};
|
||||
};
|
||||
|
||||
function normalizeMicrosoftProviderConfig(
|
||||
rawConfig: Record<string, unknown>,
|
||||
): MicrosoftProviderConfig {
|
||||
@@ -114,9 +103,10 @@ function buildMicrosoftVoiceHeaders(): Record<string, string> {
|
||||
};
|
||||
}
|
||||
|
||||
function formatMicrosoftVoiceDescription(entry: MicrosoftVoiceListEntry): string | undefined {
|
||||
const personalities = entry.VoiceTag?.VoicePersonalities?.filter(Boolean) ?? [];
|
||||
return personalities.length > 0 ? personalities.join(", ") : undefined;
|
||||
function readMicrosoftVoiceTagStrings(value: unknown): string[] | undefined {
|
||||
return Array.isArray(value)
|
||||
? value.filter((entry): entry is string => typeof entry === "string" && entry.trim().length > 0)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function isCjkDominant(text: string): boolean {
|
||||
@@ -173,24 +163,29 @@ async function listMicrosoftVoices(
|
||||
});
|
||||
}
|
||||
await assertOkOrThrowProviderError(response, "Microsoft voices API error");
|
||||
const voices = await readProviderJsonResponse<MicrosoftVoiceListEntry[]>(
|
||||
response,
|
||||
"microsoft.speech-voices",
|
||||
);
|
||||
const voices = await readProviderJsonResponse<unknown>(response, "microsoft.speech-voices");
|
||||
return Array.isArray(voices)
|
||||
? voices
|
||||
.map((voice) => ({
|
||||
id: voice.ShortName?.trim() ?? "",
|
||||
name: trimToUndefined(voice.FriendlyName) ?? trimToUndefined(voice.ShortName),
|
||||
category: voice.VoiceTag?.ContentCategories?.find((value) => value.trim().length > 0),
|
||||
description: formatMicrosoftVoiceDescription(voice),
|
||||
locale: trimToUndefined(voice.Locale),
|
||||
gender: trimToUndefined(voice.Gender),
|
||||
personalities: voice.VoiceTag?.VoicePersonalities?.filter(
|
||||
(value): value is string => value.trim().length > 0,
|
||||
),
|
||||
}))
|
||||
.filter((voice) => voice.id.length > 0)
|
||||
? voices.flatMap((value) => {
|
||||
const voice = asObject(value);
|
||||
const id = trimToUndefined(voice?.ShortName);
|
||||
if (!voice || !id) {
|
||||
return [];
|
||||
}
|
||||
const voiceTag = asObject(voice.VoiceTag);
|
||||
const categories = readMicrosoftVoiceTagStrings(voiceTag?.ContentCategories);
|
||||
const personalities = readMicrosoftVoiceTagStrings(voiceTag?.VoicePersonalities);
|
||||
return [
|
||||
{
|
||||
id,
|
||||
name: trimToUndefined(voice.FriendlyName) ?? id,
|
||||
category: categories?.[0],
|
||||
description: personalities?.length ? personalities.join(", ") : undefined,
|
||||
locale: trimToUndefined(voice.Locale),
|
||||
gender: trimToUndefined(voice.Gender),
|
||||
personalities,
|
||||
},
|
||||
];
|
||||
})
|
||||
: [];
|
||||
} finally {
|
||||
await release();
|
||||
|
||||
Reference in New Issue
Block a user