mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
fix: validate inworld speech temperature
This commit is contained in:
@@ -160,6 +160,29 @@ describe("buildInworldSpeechProvider", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("drops malformed temperature values before synthesis", async () => {
|
||||
inworldTTSMock.mockResolvedValueOnce(Buffer.from("audio"));
|
||||
const provider = buildInworldSpeechProvider();
|
||||
|
||||
await provider.synthesize?.({
|
||||
text: "Hello",
|
||||
cfg: {} as never,
|
||||
providerConfig: {
|
||||
apiKey: "key",
|
||||
voiceId: "Sarah",
|
||||
modelId: "inworld-tts-1.5-max",
|
||||
temperature: 0,
|
||||
},
|
||||
providerOverrides: { temperature: 3 },
|
||||
target: "audio-file",
|
||||
timeoutMs: 30_000,
|
||||
});
|
||||
|
||||
expect(inworldTTSMock).toHaveBeenCalledWith(
|
||||
expect.not.objectContaining({ temperature: expect.any(Number) }),
|
||||
);
|
||||
});
|
||||
|
||||
it("synthesizes voice-note targets with native OGG_OPUS output", async () => {
|
||||
inworldTTSMock.mockResolvedValueOnce(Buffer.from("opus"));
|
||||
const provider = buildInworldSpeechProvider();
|
||||
|
||||
@@ -5,7 +5,8 @@ import type {
|
||||
SpeechProviderOverrides,
|
||||
SpeechProviderPlugin,
|
||||
} from "openclaw/plugin-sdk/speech-core";
|
||||
import { asFiniteNumber, asObject, trimToUndefined } from "openclaw/plugin-sdk/speech-core";
|
||||
import { asObject, trimToUndefined } from "openclaw/plugin-sdk/speech-core";
|
||||
import { asFiniteNumberInRange } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
DEFAULT_INWORLD_MODEL_ID,
|
||||
DEFAULT_INWORLD_VOICE_ID,
|
||||
@@ -30,6 +31,10 @@ type InworldProviderOverrides = {
|
||||
temperature?: number;
|
||||
};
|
||||
|
||||
function normalizeInworldTemperature(value: unknown): number | undefined {
|
||||
return asFiniteNumberInRange(value, { min: 0, minExclusive: true, max: 2 });
|
||||
}
|
||||
|
||||
function normalizeInworldProviderConfig(rawConfig: Record<string, unknown>): InworldProviderConfig {
|
||||
const providers = asObject(rawConfig.providers);
|
||||
const raw = asObject(providers?.inworld) ?? asObject(rawConfig.inworld);
|
||||
@@ -41,7 +46,7 @@ function normalizeInworldProviderConfig(rawConfig: Record<string, unknown>): Inw
|
||||
baseUrl: normalizeInworldBaseUrl(trimToUndefined(raw?.baseUrl)),
|
||||
voiceId: trimToUndefined(raw?.voiceId) ?? DEFAULT_INWORLD_VOICE_ID,
|
||||
modelId: trimToUndefined(raw?.modelId) ?? DEFAULT_INWORLD_MODEL_ID,
|
||||
temperature: asFiniteNumber(raw?.temperature),
|
||||
temperature: normalizeInworldTemperature(raw?.temperature),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -52,7 +57,7 @@ function readInworldProviderConfig(config: SpeechProviderConfig): InworldProvide
|
||||
baseUrl: normalizeInworldBaseUrl(trimToUndefined(config.baseUrl) ?? defaults.baseUrl),
|
||||
voiceId: trimToUndefined(config.voiceId) ?? defaults.voiceId,
|
||||
modelId: trimToUndefined(config.modelId) ?? defaults.modelId,
|
||||
temperature: asFiniteNumber(config.temperature) ?? defaults.temperature,
|
||||
temperature: normalizeInworldTemperature(config.temperature) ?? defaults.temperature,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -65,7 +70,7 @@ function readInworldOverrides(
|
||||
return {
|
||||
voiceId: trimToUndefined(overrides.voiceId ?? overrides.voice),
|
||||
modelId: trimToUndefined(overrides.modelId ?? overrides.model),
|
||||
temperature: asFiniteNumber(overrides.temperature),
|
||||
temperature: normalizeInworldTemperature(overrides.temperature),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -97,8 +102,8 @@ function parseDirectiveToken(ctx: SpeechDirectiveTokenParseContext): {
|
||||
if (!ctx.policy.allowVoiceSettings) {
|
||||
return { handled: true };
|
||||
}
|
||||
const temperature = Number(ctx.value);
|
||||
if (!Number.isFinite(temperature) || temperature < 0 || temperature > 2) {
|
||||
const temperature = normalizeInworldTemperature(Number(ctx.value));
|
||||
if (temperature === undefined) {
|
||||
return { handled: true, warnings: [`invalid Inworld temperature "${ctx.value}"`] };
|
||||
}
|
||||
return { handled: true, overrides: { temperature } };
|
||||
@@ -137,9 +142,9 @@ export function buildInworldSpeechProvider(): SpeechProviderPlugin {
|
||||
...(trimToUndefined(talkProviderConfig.modelId) == null
|
||||
? {}
|
||||
: { modelId: trimToUndefined(talkProviderConfig.modelId) }),
|
||||
...(asFiniteNumber(talkProviderConfig.temperature) == null
|
||||
...(normalizeInworldTemperature(talkProviderConfig.temperature) == null
|
||||
? {}
|
||||
: { temperature: asFiniteNumber(talkProviderConfig.temperature) }),
|
||||
: { temperature: normalizeInworldTemperature(talkProviderConfig.temperature) }),
|
||||
};
|
||||
},
|
||||
resolveTalkOverrides: ({ params }) => ({
|
||||
@@ -149,9 +154,9 @@ export function buildInworldSpeechProvider(): SpeechProviderPlugin {
|
||||
...(trimToUndefined(params.modelId) == null
|
||||
? {}
|
||||
: { modelId: trimToUndefined(params.modelId) }),
|
||||
...(asFiniteNumber(params.temperature) == null
|
||||
...(normalizeInworldTemperature(params.temperature) == null
|
||||
? {}
|
||||
: { temperature: asFiniteNumber(params.temperature) }),
|
||||
: { temperature: normalizeInworldTemperature(params.temperature) }),
|
||||
}),
|
||||
listVoices: async (req) => {
|
||||
const config = req.providerConfig ? readInworldProviderConfig(req.providerConfig) : undefined;
|
||||
|
||||
Reference in New Issue
Block a user