























@@ -17,6 +17,10 @@ import type {
1717SpeechVoiceOption,
1818} from "openclaw/plugin-sdk/speech";
1919import { asBoolean, asFiniteNumber, asObject, trimToUndefined } from "openclaw/plugin-sdk/speech";
20+import {
21+fetchWithSsrFGuard,
22+ssrfPolicyFromHttpBaseUrlAllowedHostname,
23+} from "openclaw/plugin-sdk/ssrf-runtime";
2024import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
2125import { edgeTTS, inferEdgeExtension } from "./tts.js";
2226@@ -138,39 +142,48 @@ export async function listMicrosoftVoices(): Promise<SpeechVoiceOption[]> {
138142"https://speech.platform.bing.com/consumer/speech/synthesize/readaloud/voices/list" +
139143`?trustedclienttoken=${TRUSTED_CLIENT_TOKEN}`;
140144const headers = buildMicrosoftVoiceHeaders();
141-const response = await fetch(url, {
142- headers,
145+const { response, release } = await fetchWithSsrFGuard({
146+ url,
147+init: {
148+ headers,
149+},
150+policy: ssrfPolicyFromHttpBaseUrlAllowedHostname("https://speech.platform.bing.com"),
151+auditContext: "microsoft.speech.voices",
143152});
144-if (!isDebugProxyGlobalFetchPatchInstalled()) {
145-captureHttpExchange({
146- url,
147-method: "GET",
148-requestHeaders: headers,
149- response,
150-transport: "http",
151-meta: {
152-provider: "microsoft",
153-capability: "speech-voices",
154-},
155-});
153+try {
154+if (!isDebugProxyGlobalFetchPatchInstalled()) {
155+captureHttpExchange({
156+ url,
157+method: "GET",
158+requestHeaders: headers,
159+ response,
160+transport: "http",
161+meta: {
162+provider: "microsoft",
163+capability: "speech-voices",
164+},
165+});
166+}
167+await assertOkOrThrowProviderError(response, "Microsoft voices API error");
168+const voices = (await response.json()) as MicrosoftVoiceListEntry[];
169+return Array.isArray(voices)
170+ ? voices
171+.map((voice) => ({
172+id: voice.ShortName?.trim() ?? "",
173+name: trimToUndefined(voice.FriendlyName) ?? trimToUndefined(voice.ShortName),
174+category: voice.VoiceTag?.ContentCategories?.find((value) => value.trim().length > 0),
175+description: formatMicrosoftVoiceDescription(voice),
176+locale: trimToUndefined(voice.Locale),
177+gender: trimToUndefined(voice.Gender),
178+personalities: voice.VoiceTag?.VoicePersonalities?.filter(
179+(value): value is string => value.trim().length > 0,
180+),
181+}))
182+.filter((voice) => voice.id.length > 0)
183+ : [];
184+} finally {
185+await release();
156186}
157-await assertOkOrThrowProviderError(response, "Microsoft voices API error");
158-const voices = (await response.json()) as MicrosoftVoiceListEntry[];
159-return Array.isArray(voices)
160- ? voices
161-.map((voice) => ({
162-id: voice.ShortName?.trim() ?? "",
163-name: trimToUndefined(voice.FriendlyName) ?? trimToUndefined(voice.ShortName),
164-category: voice.VoiceTag?.ContentCategories?.find((value) => value.trim().length > 0),
165-description: formatMicrosoftVoiceDescription(voice),
166-locale: trimToUndefined(voice.Locale),
167-gender: trimToUndefined(voice.Gender),
168-personalities: voice.VoiceTag?.VoicePersonalities?.filter(
169-(value): value is string => value.trim().length > 0,
170-),
171-}))
172-.filter((voice) => voice.id.length > 0)
173- : [];
174187}
175188176189export function buildMicrosoftSpeechProvider(): SpeechProviderPlugin {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。