mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 10:16:44 +00:00
fix(tts): normalize sibling provider credentials (#109051)
This commit is contained in:
@@ -265,4 +265,32 @@ describe("buildAzureSpeechProvider", () => {
|
||||
timeoutMs: 45_000,
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects blank credentials across readiness, discovery, and synthesis", async () => {
|
||||
vi.stubEnv("AZURE_SPEECH_KEY", " ");
|
||||
vi.stubEnv("AZURE_SPEECH_API_KEY", " ");
|
||||
vi.stubEnv("SPEECH_KEY", " ");
|
||||
const provider = buildAzureSpeechProvider();
|
||||
const providerConfig = { apiKey: " ", region: "eastus" };
|
||||
|
||||
expect(provider.isConfigured({ providerConfig, timeoutMs: 1_000 })).toBe(false);
|
||||
await expect(
|
||||
provider.listVoices?.({ apiKey: " ", providerConfig, timeoutMs: 1_000 }),
|
||||
).rejects.toThrow("Azure Speech API key missing");
|
||||
|
||||
const request = {
|
||||
text: "hello",
|
||||
cfg: {} as never,
|
||||
providerConfig,
|
||||
target: "audio-file" as const,
|
||||
timeoutMs: 1_000,
|
||||
};
|
||||
await expect(provider.synthesize(request)).rejects.toThrow("Azure Speech API key missing");
|
||||
await expect(provider.synthesizeTelephony?.(request)).rejects.toThrow(
|
||||
"Azure Speech API key missing",
|
||||
);
|
||||
|
||||
expect(listAzureSpeechVoicesMock).not.toHaveBeenCalled();
|
||||
expect(azureSpeechTTSMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,7 +9,12 @@ import type {
|
||||
SpeechProviderOverrides,
|
||||
SpeechProviderPlugin,
|
||||
} from "openclaw/plugin-sdk/speech-core";
|
||||
import { asFiniteNumber, asObject, trimToUndefined } from "openclaw/plugin-sdk/speech-core";
|
||||
import {
|
||||
asFiniteNumber,
|
||||
asObject,
|
||||
resolveSpeechProviderApiKey,
|
||||
trimToUndefined,
|
||||
} from "openclaw/plugin-sdk/speech-core";
|
||||
import {
|
||||
azureSpeechTTS,
|
||||
DEFAULT_AZURE_SPEECH_AUDIO_FORMAT,
|
||||
@@ -175,8 +180,8 @@ function parseDirectiveToken(ctx: SpeechDirectiveTokenParseContext): {
|
||||
}
|
||||
}
|
||||
|
||||
function resolveApiKey(config: AzureSpeechProviderConfig): string | undefined {
|
||||
return config.apiKey ?? readAzureSpeechEnvApiKey();
|
||||
function resolveApiKey(...candidates: Array<string | undefined>): string | undefined {
|
||||
return resolveSpeechProviderApiKey(...candidates, readAzureSpeechEnvApiKey());
|
||||
}
|
||||
|
||||
function resolveTimeoutMs(config: AzureSpeechProviderConfig, timeoutMs: number): number {
|
||||
@@ -250,7 +255,9 @@ export function buildAzureSpeechProvider(): SpeechProviderPlugin {
|
||||
const config = req.providerConfig
|
||||
? readAzureSpeechProviderConfig(req.providerConfig)
|
||||
: undefined;
|
||||
const apiKey = req.apiKey ?? (config ? resolveApiKey(config) : readAzureSpeechEnvApiKey());
|
||||
const requestValue = req.apiKey;
|
||||
const configValue = config?.apiKey;
|
||||
const apiKey = resolveApiKey(requestValue, configValue);
|
||||
if (!apiKey) {
|
||||
throw new Error("Azure Speech API key missing");
|
||||
}
|
||||
@@ -264,12 +271,14 @@ export function buildAzureSpeechProvider(): SpeechProviderPlugin {
|
||||
},
|
||||
isConfigured: ({ providerConfig }) => {
|
||||
const config = readAzureSpeechProviderConfig(providerConfig);
|
||||
return Boolean(resolveApiKey(config) && (config.baseUrl || config.region || config.endpoint));
|
||||
return Boolean(
|
||||
resolveApiKey(config.apiKey) && (config.baseUrl || config.region || config.endpoint),
|
||||
);
|
||||
},
|
||||
synthesize: async (req) => {
|
||||
const config = readAzureSpeechProviderConfig(req.providerConfig);
|
||||
const overrides = readAzureSpeechOverrides(req.providerOverrides);
|
||||
const apiKey = resolveApiKey(config);
|
||||
const apiKey = resolveApiKey(config.apiKey);
|
||||
if (!apiKey) {
|
||||
throw new Error("Azure Speech API key missing");
|
||||
}
|
||||
@@ -298,7 +307,7 @@ export function buildAzureSpeechProvider(): SpeechProviderPlugin {
|
||||
synthesizeTelephony: async (req) => {
|
||||
const config = readAzureSpeechProviderConfig(req.providerConfig);
|
||||
const overrides = readAzureSpeechOverrides(req.providerOverrides);
|
||||
const apiKey = resolveApiKey(config);
|
||||
const apiKey = resolveApiKey(config.apiKey);
|
||||
if (!apiKey) {
|
||||
throw new Error("Azure Speech API key missing");
|
||||
}
|
||||
|
||||
@@ -5,6 +5,10 @@ import { buildElevenLabsSpeechProvider } from "./speech-provider.js";
|
||||
|
||||
const fetchWithSsrFGuardMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("./config-api.js", () => ({
|
||||
resolveElevenLabsApiKeyWithProfileFallback: () => null,
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/ssrf-runtime", () => ({
|
||||
fetchWithSsrFGuard: async (params: {
|
||||
url: string;
|
||||
@@ -42,6 +46,7 @@ describe("elevenlabs speech provider", () => {
|
||||
afterEach(() => {
|
||||
globalThis.fetch = originalFetch;
|
||||
fetchWithSsrFGuardMock.mockClear();
|
||||
vi.unstubAllEnvs();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
@@ -72,6 +77,35 @@ describe("elevenlabs speech provider", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects blank credentials across discovery and synthesis before requests", async () => {
|
||||
vi.stubEnv("ELEVENLABS_API_KEY", " ");
|
||||
vi.stubEnv("XI_API_KEY", " ");
|
||||
const provider = buildElevenLabsSpeechProvider();
|
||||
const providerConfig = { apiKey: " " };
|
||||
|
||||
expect(provider.isConfigured({ providerConfig, timeoutMs: 1_000 })).toBe(false);
|
||||
await expect(
|
||||
provider.listVoices?.({ apiKey: " ", providerConfig, timeoutMs: 1_000 }),
|
||||
).rejects.toThrow("ElevenLabs API key missing");
|
||||
|
||||
const request = {
|
||||
text: "hello",
|
||||
cfg: {} as never,
|
||||
providerConfig,
|
||||
target: "audio-file" as const,
|
||||
timeoutMs: 1_000,
|
||||
};
|
||||
await expect(provider.synthesize(request)).rejects.toThrow("ElevenLabs API key missing");
|
||||
await expect(provider.streamSynthesize?.(request)).rejects.toThrow(
|
||||
"ElevenLabs API key missing",
|
||||
);
|
||||
await expect(provider.synthesizeTelephony?.(request)).rejects.toThrow(
|
||||
"ElevenLabs API key missing",
|
||||
);
|
||||
|
||||
expect(fetchWithSsrFGuardMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps non-equivalent deprecated ElevenLabs TTS model IDs", async () => {
|
||||
const provider = buildElevenLabsSpeechProvider();
|
||||
const fetchMock = vi.fn(async (_url: string, init?: RequestInit) => {
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
requireInRange,
|
||||
trimToUndefined,
|
||||
} from "openclaw/plugin-sdk/speech";
|
||||
import { resolveSpeechProviderApiKey } from "openclaw/plugin-sdk/speech-core";
|
||||
import {
|
||||
fetchWithSsrFGuard,
|
||||
ssrfPolicyFromHttpBaseUrlAllowedHostname,
|
||||
@@ -187,6 +188,24 @@ function readElevenLabsProviderConfig(config: SpeechProviderConfig): ElevenLabsP
|
||||
};
|
||||
}
|
||||
|
||||
function resolveElevenLabsApiKey(...candidates: Array<string | undefined>): string | undefined {
|
||||
return resolveSpeechProviderApiKey(
|
||||
...candidates,
|
||||
resolveElevenLabsApiKeyWithProfileFallback() ?? undefined,
|
||||
process.env.XI_API_KEY,
|
||||
);
|
||||
}
|
||||
|
||||
function resolveElevenLabsTalkApiKey(config: SpeechProviderConfig): string | undefined {
|
||||
if (config.apiKey === undefined) {
|
||||
return resolveElevenLabsApiKey();
|
||||
}
|
||||
return normalizeResolvedSecretInputString({
|
||||
value: config.apiKey,
|
||||
path: "talk.providers.elevenlabs.apiKey",
|
||||
});
|
||||
}
|
||||
|
||||
function mergeVoiceSettingsOverride(
|
||||
ctx: SpeechDirectiveTokenParseContext,
|
||||
next: Record<string, unknown>,
|
||||
@@ -406,8 +425,7 @@ function resolveElevenLabsTtsRequest(
|
||||
): Parameters<typeof elevenLabsTTS>[0] {
|
||||
const config = readElevenLabsProviderConfig(req.providerConfig);
|
||||
const overrides = req.providerOverrides ?? {};
|
||||
const apiKey =
|
||||
config.apiKey || resolveElevenLabsApiKeyWithProfileFallback() || process.env.XI_API_KEY;
|
||||
const apiKey = resolveElevenLabsApiKey(config.apiKey);
|
||||
if (!apiKey) {
|
||||
throw new Error("ElevenLabs API key missing");
|
||||
}
|
||||
@@ -441,13 +459,7 @@ export function buildElevenLabsSpeechProvider(): SpeechProviderPlugin {
|
||||
resolveTalkConfig: ({ baseTtsConfig, talkProviderConfig }) => {
|
||||
const base = normalizeElevenLabsProviderConfig(baseTtsConfig);
|
||||
const talkVoiceSettings = asObject(talkProviderConfig.voiceSettings);
|
||||
const resolvedTalkApiKey =
|
||||
talkProviderConfig.apiKey === undefined
|
||||
? (resolveElevenLabsApiKeyWithProfileFallback() ?? undefined)
|
||||
: normalizeResolvedSecretInputString({
|
||||
value: talkProviderConfig.apiKey,
|
||||
path: "talk.providers.elevenlabs.apiKey",
|
||||
});
|
||||
const resolvedTalkApiKey = resolveElevenLabsTalkApiKey(talkProviderConfig);
|
||||
return {
|
||||
...base,
|
||||
...(resolvedTalkApiKey === undefined ? {} : { apiKey: resolvedTalkApiKey }),
|
||||
@@ -529,11 +541,9 @@ export function buildElevenLabsSpeechProvider(): SpeechProviderPlugin {
|
||||
const config = req.providerConfig
|
||||
? readElevenLabsProviderConfig(req.providerConfig)
|
||||
: undefined;
|
||||
const apiKey =
|
||||
req.apiKey ||
|
||||
config?.apiKey ||
|
||||
resolveElevenLabsApiKeyWithProfileFallback() ||
|
||||
process.env.XI_API_KEY;
|
||||
const requestValue = req.apiKey;
|
||||
const configValue = config?.apiKey;
|
||||
const apiKey = resolveElevenLabsApiKey(requestValue, configValue);
|
||||
if (!apiKey) {
|
||||
throw new Error("ElevenLabs API key missing");
|
||||
}
|
||||
@@ -544,11 +554,7 @@ export function buildElevenLabsSpeechProvider(): SpeechProviderPlugin {
|
||||
});
|
||||
},
|
||||
isConfigured: ({ providerConfig }) =>
|
||||
Boolean(
|
||||
readElevenLabsProviderConfig(providerConfig).apiKey ||
|
||||
resolveElevenLabsApiKeyWithProfileFallback() ||
|
||||
process.env.XI_API_KEY,
|
||||
),
|
||||
Boolean(resolveElevenLabsApiKey(readElevenLabsProviderConfig(providerConfig).apiKey)),
|
||||
synthesize: async (req) => {
|
||||
const overrides = req.providerOverrides ?? {};
|
||||
const outputFormat =
|
||||
|
||||
@@ -9,6 +9,7 @@ import type {
|
||||
import {
|
||||
asObject,
|
||||
parseSpeechDirectiveNumberOverride,
|
||||
resolveSpeechProviderApiKey,
|
||||
trimToUndefined,
|
||||
} from "openclaw/plugin-sdk/speech-core";
|
||||
import { asFiniteNumberInRange } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
@@ -93,13 +94,23 @@ function normalizeVolcengineProviderConfig(
|
||||
}
|
||||
|
||||
function resolveSeedSpeechApiKey(configApiKey?: string): string | undefined {
|
||||
return (
|
||||
configApiKey ??
|
||||
trimToUndefined(process.env.VOLCENGINE_TTS_API_KEY) ??
|
||||
trimToUndefined(process.env.BYTEPLUS_SEED_SPEECH_API_KEY)
|
||||
return resolveSpeechProviderApiKey(
|
||||
configApiKey,
|
||||
process.env.VOLCENGINE_TTS_API_KEY,
|
||||
process.env.BYTEPLUS_SEED_SPEECH_API_KEY,
|
||||
);
|
||||
}
|
||||
|
||||
function resolveLegacyVolcengineCredentials(config: {
|
||||
appId?: string;
|
||||
token?: string;
|
||||
}): Pick<VolcengineTtsProviderConfig, "appId" | "token"> {
|
||||
return {
|
||||
appId: trimToUndefined(config.appId) ?? trimToUndefined(process.env.VOLCENGINE_TTS_APPID),
|
||||
token: resolveSpeechProviderApiKey(config.token, process.env.VOLCENGINE_TTS_TOKEN),
|
||||
};
|
||||
}
|
||||
|
||||
function readProviderConfig(config: SpeechProviderConfig): VolcengineTtsProviderConfig {
|
||||
const normalized = normalizeVolcengineProviderConfig({});
|
||||
return {
|
||||
@@ -187,19 +198,15 @@ export function buildVolcengineSpeechProvider(): SpeechProviderPlugin {
|
||||
|
||||
isConfigured: ({ providerConfig }) => {
|
||||
const cfg = readProviderConfig(providerConfig);
|
||||
return Boolean(
|
||||
resolveSeedSpeechApiKey(cfg.apiKey) ||
|
||||
((cfg.appId || process.env.VOLCENGINE_TTS_APPID) &&
|
||||
(cfg.token || process.env.VOLCENGINE_TTS_TOKEN)),
|
||||
);
|
||||
const legacy = resolveLegacyVolcengineCredentials(cfg);
|
||||
return Boolean(resolveSeedSpeechApiKey(cfg.apiKey) || (legacy.appId && legacy.token));
|
||||
},
|
||||
|
||||
synthesize: async (req) => {
|
||||
const cfg = readProviderConfig(req.providerConfig);
|
||||
const overrides = readVolcengineOverrides(req.providerOverrides);
|
||||
const apiKey = resolveSeedSpeechApiKey(cfg.apiKey);
|
||||
const appId = cfg.appId || process.env.VOLCENGINE_TTS_APPID;
|
||||
const token = cfg.token || process.env.VOLCENGINE_TTS_TOKEN;
|
||||
const { appId, token } = resolveLegacyVolcengineCredentials(cfg);
|
||||
|
||||
if (!apiKey && (!appId || !token)) {
|
||||
throw new Error(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Volcengine tests cover tts plugin behavior.
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { buildVolcengineSpeechProvider } from "./speech-provider.js";
|
||||
import { volcengineTTS } from "./tts.js";
|
||||
|
||||
@@ -39,13 +39,12 @@ function makeLegacyProviderConfig(overrides?: Record<string, unknown>) {
|
||||
};
|
||||
}
|
||||
|
||||
function clearTtsEnv() {
|
||||
delete process.env.BYTEPLUS_API_KEY;
|
||||
delete process.env.BYTEPLUS_SEED_SPEECH_API_KEY;
|
||||
delete process.env.VOLCENGINE_TTS_API_KEY;
|
||||
delete process.env.VOLCENGINE_TTS_APPID;
|
||||
delete process.env.VOLCENGINE_TTS_TOKEN;
|
||||
}
|
||||
const TTS_ENV_KEYS = [
|
||||
"BYTEPLUS_SEED_SPEECH_API_KEY",
|
||||
"VOLCENGINE_TTS_API_KEY",
|
||||
"VOLCENGINE_TTS_APPID",
|
||||
"VOLCENGINE_TTS_TOKEN",
|
||||
] as const;
|
||||
|
||||
function makeOversizedStreamResponse(): Response {
|
||||
return new Response(
|
||||
@@ -59,19 +58,18 @@ function makeOversizedStreamResponse(): Response {
|
||||
);
|
||||
}
|
||||
|
||||
function restoreOptionalEnv(key: string, value: string | undefined) {
|
||||
if (value === undefined) {
|
||||
delete process.env[key];
|
||||
} else {
|
||||
process.env[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
describe("Volcengine speech provider", () => {
|
||||
const provider = buildVolcengineSpeechProvider();
|
||||
|
||||
beforeEach(() => {
|
||||
fetchWithSsrFGuardMock.mockReset();
|
||||
for (const key of TTS_ENV_KEYS) {
|
||||
vi.stubEnv(key, undefined);
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
it("has correct id, label, and aliases", () => {
|
||||
@@ -94,40 +92,32 @@ describe("Volcengine speech provider", () => {
|
||||
});
|
||||
|
||||
it("reports not configured when credentials are missing", () => {
|
||||
const oldBytePlusKey = process.env.BYTEPLUS_API_KEY;
|
||||
const oldSeedKey = process.env.BYTEPLUS_SEED_SPEECH_API_KEY;
|
||||
const oldApiKey = process.env.VOLCENGINE_TTS_API_KEY;
|
||||
const oldAppId = process.env.VOLCENGINE_TTS_APPID;
|
||||
const oldToken = process.env.VOLCENGINE_TTS_TOKEN;
|
||||
clearTtsEnv();
|
||||
try {
|
||||
expect(provider.isConfigured({ providerConfig: {}, timeoutMs: 30000 })).toBe(false);
|
||||
} finally {
|
||||
restoreOptionalEnv("BYTEPLUS_API_KEY", oldBytePlusKey);
|
||||
restoreOptionalEnv("BYTEPLUS_SEED_SPEECH_API_KEY", oldSeedKey);
|
||||
restoreOptionalEnv("VOLCENGINE_TTS_API_KEY", oldApiKey);
|
||||
restoreOptionalEnv("VOLCENGINE_TTS_APPID", oldAppId);
|
||||
restoreOptionalEnv("VOLCENGINE_TTS_TOKEN", oldToken);
|
||||
}
|
||||
expect(provider.isConfigured({ providerConfig: {}, timeoutMs: 30000 })).toBe(false);
|
||||
});
|
||||
|
||||
it("falls back to env vars for credentials", () => {
|
||||
const oldBytePlusKey = process.env.BYTEPLUS_API_KEY;
|
||||
const oldSeedKey = process.env.BYTEPLUS_SEED_SPEECH_API_KEY;
|
||||
const oldApiKey = process.env.VOLCENGINE_TTS_API_KEY;
|
||||
const oldAppId = process.env.VOLCENGINE_TTS_APPID;
|
||||
const oldToken = process.env.VOLCENGINE_TTS_TOKEN;
|
||||
clearTtsEnv();
|
||||
process.env.BYTEPLUS_SEED_SPEECH_API_KEY = "env-api-key";
|
||||
try {
|
||||
expect(provider.isConfigured({ providerConfig: {}, timeoutMs: 30000 })).toBe(true);
|
||||
} finally {
|
||||
restoreOptionalEnv("BYTEPLUS_API_KEY", oldBytePlusKey);
|
||||
restoreOptionalEnv("BYTEPLUS_SEED_SPEECH_API_KEY", oldSeedKey);
|
||||
restoreOptionalEnv("VOLCENGINE_TTS_API_KEY", oldApiKey);
|
||||
restoreOptionalEnv("VOLCENGINE_TTS_APPID", oldAppId);
|
||||
restoreOptionalEnv("VOLCENGINE_TTS_TOKEN", oldToken);
|
||||
vi.stubEnv("BYTEPLUS_SEED_SPEECH_API_KEY", "env-api-key");
|
||||
expect(provider.isConfigured({ providerConfig: {}, timeoutMs: 30000 })).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects blank Seed and legacy credentials before requests", async () => {
|
||||
for (const key of TTS_ENV_KEYS) {
|
||||
vi.stubEnv(key, " ");
|
||||
}
|
||||
const providerConfig = { apiKey: " ", appId: " ", token: " " };
|
||||
|
||||
expect(provider.isConfigured({ providerConfig, timeoutMs: 30_000 })).toBe(false);
|
||||
await expect(
|
||||
provider.synthesize({
|
||||
text: "hello",
|
||||
cfg: {},
|
||||
providerConfig,
|
||||
target: "audio-file",
|
||||
timeoutMs: 1_000,
|
||||
}),
|
||||
).rejects.toThrow("Volcengine TTS credentials missing");
|
||||
|
||||
expect(fetchWithSsrFGuardMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("lists voices with locale and gender", async () => {
|
||||
|
||||
@@ -268,6 +268,29 @@ describe("xai speech provider", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("treats blank direct credentials as absent across readiness and requests", async () => {
|
||||
process.env.XAI_API_KEY = " ";
|
||||
const provider = buildXaiSpeechProvider();
|
||||
const providerConfig = { apiKey: " " };
|
||||
|
||||
expect(provider.isConfigured({ cfg: {}, providerConfig, timeoutMs: 5_000 })).toBe(false);
|
||||
await expect(provider.listVoices?.({ apiKey: " ", providerConfig })).resolves.toEqual(
|
||||
["ara", "eve", "leo", "rex", "sal"].map((voice) => ({ id: voice, name: voice })),
|
||||
);
|
||||
await expect(
|
||||
provider.synthesize({
|
||||
text: "hello",
|
||||
cfg: {},
|
||||
providerConfig,
|
||||
target: "audio-file",
|
||||
timeoutMs: 5_000,
|
||||
}),
|
||||
).rejects.toThrow("xAI credentials missing for TTS");
|
||||
|
||||
expect(listXaiTtsVoicesMock).not.toHaveBeenCalled();
|
||||
expect(xaiTTSMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("reports not configured when there is no apiKey, env, or auth profile", () => {
|
||||
isProviderAuthProfileConfiguredMock.mockReturnValue(false);
|
||||
const provider = buildXaiSpeechProvider();
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
type SpeechProviderPlugin,
|
||||
type SpeechSynthesisTarget,
|
||||
} from "openclaw/plugin-sdk/speech";
|
||||
import { resolveSpeechProviderApiKey } from "openclaw/plugin-sdk/speech-core";
|
||||
import {
|
||||
asFiniteNumberInRange,
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
@@ -136,6 +137,10 @@ function readXaiOverrides(overrides: SpeechProviderOverrides | undefined): XaiTt
|
||||
};
|
||||
}
|
||||
|
||||
function resolveDirectXaiAudioApiKey(configApiKey?: string): string | undefined {
|
||||
return resolveSpeechProviderApiKey(configApiKey, process.env.XAI_API_KEY);
|
||||
}
|
||||
|
||||
function resolveGeneratedAudioMaxBytes(req: {
|
||||
cfg: { agents?: { defaults?: { mediaMaxMb?: number } } };
|
||||
}): number {
|
||||
@@ -240,7 +245,7 @@ export function buildXaiSpeechProvider(): SpeechProviderPlugin {
|
||||
});
|
||||
},
|
||||
isConfigured: ({ providerConfig, cfg }) =>
|
||||
Boolean(readXaiProviderConfig(providerConfig).apiKey || process.env.XAI_API_KEY) ||
|
||||
Boolean(resolveDirectXaiAudioApiKey(readXaiProviderConfig(providerConfig).apiKey)) ||
|
||||
isProviderAuthProfileConfigured({ provider: "xai", cfg }),
|
||||
synthesize: async (req) => {
|
||||
const config = readXaiProviderConfig(req.providerConfig);
|
||||
@@ -319,7 +324,7 @@ async function resolveOptionalXaiAudioApiKey(
|
||||
configApiKey: string | undefined,
|
||||
cfg?: OpenClawConfig,
|
||||
): Promise<string | undefined> {
|
||||
const direct = trimToUndefined(configApiKey) ?? trimToUndefined(process.env.XAI_API_KEY);
|
||||
const direct = resolveDirectXaiAudioApiKey(configApiKey);
|
||||
if (direct) {
|
||||
return direct;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user