mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-02 15:51:35 +00:00
* refactor(onboarding): remove search setup barrel * refactor(plugins): reuse detected package manifest * test(update): replace global helper bridges * refactor(talk): remove dead legacy response fallback * refactor(protocol): derive root types from schema The maintainer approved broadening the additive schema-backed type surface without a protocol version bump. * fix(plugins): drop stale package path import * test(protocol): type dynamic registry lookups * docs(talk): explain canonical response boundary * refactor(talk): enforce canonical response input * fix(protocol): keep root type exports registry-free * refactor(update): expose helpers through test facades * fix(protocol): keep result types on leaf schema modules
261 lines
6.6 KiB
TypeScript
261 lines
6.6 KiB
TypeScript
// Verifies talk-mode config normalization behavior.
|
|
import { describe, expect, it } from "vitest";
|
|
import { TALK_TEST_PROVIDER_ID } from "../test-utils/talk-test-provider.js";
|
|
import { buildTalkConfigResponse, normalizeTalkSection } from "./talk.js";
|
|
|
|
describe("talk normalization", () => {
|
|
it("preserves the explicit ambient Talk agent", () => {
|
|
expect(normalizeTalkSection({ agentId: " ops " })).toEqual({ agentId: "ops" });
|
|
expect(buildTalkConfigResponse({ agentId: "ops" })).toEqual({ agentId: "ops" });
|
|
});
|
|
|
|
it("keeps core Talk normalization generic and ignores legacy provider-flat fields", () => {
|
|
const normalized = normalizeTalkSection({
|
|
voiceId: "voice-123",
|
|
voiceAliases: { Clawd: "VoiceAlias1234567890" },
|
|
modelId: "eleven_v3",
|
|
outputFormat: "pcm_44100",
|
|
apiKey: "secret-key", // pragma: allowlist secret
|
|
consultThinkingLevel: " low ",
|
|
consultFastMode: true,
|
|
speechLocale: " ru-RU ",
|
|
interruptOnSpeech: false,
|
|
silenceTimeoutMs: 1500,
|
|
} as unknown as never);
|
|
|
|
expect(normalized).toEqual({
|
|
speechLocale: "ru-RU",
|
|
consultThinkingLevel: "low",
|
|
consultFastMode: true,
|
|
interruptOnSpeech: false,
|
|
silenceTimeoutMs: 1500,
|
|
});
|
|
});
|
|
|
|
it("uses new provider/providers shape directly when present", () => {
|
|
const normalized = normalizeTalkSection({
|
|
provider: "acme",
|
|
providers: {
|
|
acme: {
|
|
voiceId: "acme-voice",
|
|
custom: true,
|
|
},
|
|
},
|
|
realtime: {
|
|
provider: "openai",
|
|
providers: {
|
|
openai: {
|
|
model: "gpt-realtime",
|
|
},
|
|
},
|
|
model: "gpt-realtime",
|
|
speakerVoice: "alloy",
|
|
speakerVoiceId: "voice-123",
|
|
mode: "realtime",
|
|
transport: "webrtc",
|
|
vadThreshold: 0.45,
|
|
silenceDurationMs: 650,
|
|
prefixPaddingMs: 250,
|
|
reasoningEffort: " low ",
|
|
brain: "agent-consult",
|
|
consultRouting: "force-agent-consult",
|
|
},
|
|
interruptOnSpeech: true,
|
|
});
|
|
|
|
expect(normalized).toEqual({
|
|
provider: "acme",
|
|
providers: {
|
|
acme: {
|
|
voiceId: "acme-voice",
|
|
custom: true,
|
|
},
|
|
},
|
|
realtime: {
|
|
provider: "openai",
|
|
providers: {
|
|
openai: {
|
|
model: "gpt-realtime",
|
|
},
|
|
},
|
|
model: "gpt-realtime",
|
|
speakerVoice: "alloy",
|
|
speakerVoiceId: "voice-123",
|
|
mode: "realtime",
|
|
transport: "webrtc",
|
|
vadThreshold: 0.45,
|
|
silenceDurationMs: 650,
|
|
prefixPaddingMs: 250,
|
|
reasoningEffort: "low",
|
|
brain: "agent-consult",
|
|
consultRouting: "force-agent-consult",
|
|
},
|
|
interruptOnSpeech: true,
|
|
});
|
|
});
|
|
|
|
it("drops invalid realtime voice detection defaults", () => {
|
|
const normalized = normalizeTalkSection({
|
|
realtime: {
|
|
vadThreshold: 1.5,
|
|
silenceDurationMs: 0,
|
|
prefixPaddingMs: -1,
|
|
reasoningEffort: " ",
|
|
},
|
|
} as never);
|
|
|
|
expect(normalized).toBeUndefined();
|
|
});
|
|
|
|
it("merges duplicate provider ids after trimming", () => {
|
|
const normalized = normalizeTalkSection({
|
|
provider: " elevenlabs ",
|
|
providers: {
|
|
" elevenlabs ": {
|
|
voiceId: "voice-123",
|
|
},
|
|
elevenlabs: {
|
|
apiKey: "secret-key",
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(normalized).toEqual({
|
|
provider: "elevenlabs",
|
|
providers: {
|
|
elevenlabs: {
|
|
voiceId: "voice-123",
|
|
apiKey: "secret-key",
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
it("builds a canonical resolved talk payload for clients", () => {
|
|
const payload = buildTalkConfigResponse({
|
|
provider: "acme",
|
|
providers: {
|
|
acme: {
|
|
voiceId: "acme-voice",
|
|
modelId: "acme-model",
|
|
},
|
|
},
|
|
speechLocale: "ru-RU",
|
|
interruptOnSpeech: true,
|
|
});
|
|
|
|
expect(payload).toEqual({
|
|
provider: "acme",
|
|
providers: {
|
|
acme: {
|
|
voiceId: "acme-voice",
|
|
modelId: "acme-model",
|
|
},
|
|
},
|
|
resolved: {
|
|
provider: "acme",
|
|
config: {
|
|
voiceId: "acme-voice",
|
|
modelId: "acme-model",
|
|
},
|
|
},
|
|
speechLocale: "ru-RU",
|
|
interruptOnSpeech: true,
|
|
});
|
|
});
|
|
|
|
it("preserves normalized realtime instructions in talk.config payloads", () => {
|
|
const payload = buildTalkConfigResponse(
|
|
normalizeTalkSection({
|
|
realtime: {
|
|
provider: "openai",
|
|
providers: {
|
|
openai: {
|
|
model: "gpt-realtime",
|
|
speakerVoice: "alloy",
|
|
},
|
|
},
|
|
instructions: " Speak with crisp diction. ",
|
|
},
|
|
}),
|
|
);
|
|
|
|
expect(payload?.realtime?.provider).toBe("openai");
|
|
expect(payload?.realtime?.instructions).toBe("Speak with crisp diction.");
|
|
});
|
|
|
|
it("does not report an active provider when the configured speech provider cannot resolve", () => {
|
|
const mismatchPayload = buildTalkConfigResponse({
|
|
provider: "acme",
|
|
providers: {
|
|
elevenlabs: {
|
|
voiceId: "voice-123",
|
|
},
|
|
},
|
|
});
|
|
expect(mismatchPayload).toEqual({
|
|
providers: {
|
|
elevenlabs: {
|
|
voiceId: "voice-123",
|
|
},
|
|
},
|
|
});
|
|
|
|
const ambiguousPayload = buildTalkConfigResponse({
|
|
providers: {
|
|
acme: {
|
|
voiceId: "voice-acme",
|
|
},
|
|
elevenlabs: {
|
|
voiceId: "voice-123",
|
|
},
|
|
},
|
|
});
|
|
expect(ambiguousPayload).toEqual({
|
|
providers: {
|
|
acme: {
|
|
voiceId: "voice-acme",
|
|
},
|
|
elevenlabs: {
|
|
voiceId: "voice-123",
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
it.each(["constructor", "__proto__"])(
|
|
"does not resolve inherited Object.prototype provider key %s",
|
|
(provider) => {
|
|
const payload = buildTalkConfigResponse({
|
|
provider,
|
|
providers: {
|
|
elevenlabs: { voiceId: "voice-123" },
|
|
},
|
|
});
|
|
|
|
expect(payload?.resolved).toBeUndefined();
|
|
expect(payload?.provider).toBeUndefined();
|
|
},
|
|
);
|
|
|
|
it("preserves SecretRef apiKey values during normalization", () => {
|
|
const normalized = normalizeTalkSection({
|
|
provider: TALK_TEST_PROVIDER_ID,
|
|
providers: {
|
|
[TALK_TEST_PROVIDER_ID]: {
|
|
apiKey: { source: "env", provider: "default", id: "ELEVENLABS_API_KEY" },
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(normalized).toEqual({
|
|
provider: TALK_TEST_PROVIDER_ID,
|
|
providers: {
|
|
[TALK_TEST_PROVIDER_ID]: {
|
|
apiKey: { source: "env", provider: "default", id: "ELEVENLABS_API_KEY" },
|
|
},
|
|
},
|
|
});
|
|
});
|
|
});
|