mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-11 01:01:13 +00:00
refactor: move talk config contract under plugin
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
d5a737eb69a2b2b64526fa0197ef9fe576b1d5d4b949a5c610a8457d5f5706cd config-baseline.json
|
||||
1dc927cd4be5a0ef6e17958a53ceb6df155107ca8100cdb4d417003483f17990 config-baseline.json
|
||||
b1a181b667568b5860a80945837d544fdec4f946fba34e871936ce0cd3eb689b config-baseline.core.json
|
||||
3c999707b167138de34f6255e3488b99e404c5132d3fc5879a1fa12d815c31f5 config-baseline.channel.json
|
||||
031b237717ca108ea2cd314413db4c91edfdfea55f808179e3066331f41af134 config-baseline.plugin.json
|
||||
fcf32a00815f392ceda9195b8c2af82ae7e88da333feaacee9296f7d5921e73f config-baseline.plugin.json
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
eedd483d35cebcdf261d0b550185e57aeb23a36446c89f5c76a038d6e6d2651a plugin-sdk-api-baseline.json
|
||||
7713278ccd37a88115baac658ae9cb381bdaac8ad0bc2b7b79956b83819c9973 plugin-sdk-api-baseline.jsonl
|
||||
924468503f0a1b9d6338dcf086c556db83c479ea78c5d9fb6ee7f36434bb3425 plugin-sdk-api-baseline.json
|
||||
24220dbc9f18c092304321fd3064de39254e87848a6f5ba673b5652f986e36e0 plugin-sdk-api-baseline.jsonl
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export {
|
||||
ELEVENLABS_TALK_PROVIDER_ID,
|
||||
ELEVENLABS_TALK_LEGACY_CONFIG_RULES,
|
||||
hasLegacyTalkFields,
|
||||
legacyConfigRules,
|
||||
normalizeCompatibilityConfig,
|
||||
} from "./doctor-contract.js";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { ChannelDoctorLegacyConfigRule } from "openclaw/plugin-sdk/channel-contract";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { ELEVENLABS_TALK_PROVIDER_ID, migrateElevenLabsLegacyTalkConfig } from "./config-compat.js";
|
||||
|
||||
@@ -5,7 +6,7 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function hasLegacyTalkFields(value: unknown): boolean {
|
||||
export function hasLegacyTalkFields(value: unknown): boolean {
|
||||
const talk = isRecord(value) ? value : null;
|
||||
if (!talk) {
|
||||
return false;
|
||||
@@ -15,14 +16,16 @@ function hasLegacyTalkFields(value: unknown): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
export const legacyConfigRules = [
|
||||
export const legacyConfigRules: ChannelDoctorLegacyConfigRule[] = [
|
||||
{
|
||||
path: ["talk"],
|
||||
message:
|
||||
"talk.voiceId/talk.voiceAliases/talk.modelId/talk.outputFormat/talk.apiKey are legacy; use talk.providers.<provider> (auto-migrated on load).",
|
||||
match: hasLegacyTalkFields,
|
||||
},
|
||||
] as const;
|
||||
];
|
||||
|
||||
export const ELEVENLABS_TALK_LEGACY_CONFIG_RULES = legacyConfigRules;
|
||||
|
||||
export function normalizeCompatibilityConfig({ cfg }: { cfg: OpenClawConfig }): {
|
||||
config: OpenClawConfig;
|
||||
|
||||
@@ -335,6 +335,10 @@
|
||||
"types": "./dist/plugin-sdk/bluebubbles-policy.d.ts",
|
||||
"default": "./dist/plugin-sdk/bluebubbles-policy.js"
|
||||
},
|
||||
"./plugin-sdk/elevenlabs": {
|
||||
"types": "./dist/plugin-sdk/elevenlabs.d.ts",
|
||||
"default": "./dist/plugin-sdk/elevenlabs.js"
|
||||
},
|
||||
"./plugin-sdk/browser-config-support": {
|
||||
"types": "./dist/plugin-sdk/browser-config-support.d.ts",
|
||||
"default": "./dist/plugin-sdk/browser-config-support.js"
|
||||
|
||||
@@ -71,6 +71,9 @@ export const pluginSdkDocMetadata = {
|
||||
"provider-onboard": {
|
||||
category: "provider",
|
||||
},
|
||||
elevenlabs: {
|
||||
category: "provider",
|
||||
},
|
||||
"runtime-store": {
|
||||
category: "runtime",
|
||||
},
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
"allowlist-config-edit",
|
||||
"bluebubbles",
|
||||
"bluebubbles-policy",
|
||||
"elevenlabs",
|
||||
"browser-config-support",
|
||||
"browser-support",
|
||||
"boolean-param",
|
||||
|
||||
@@ -761,7 +761,9 @@ describe("normalizeCompatibilityConfigValues", () => {
|
||||
interruptOnSpeech: false,
|
||||
silenceTimeoutMs: 1500,
|
||||
});
|
||||
expect(res.changes).toEqual(["Moved legacy talk flat fields → talk.providers.elevenlabs."]);
|
||||
expect(res.changes).toEqual([
|
||||
"Moved talk legacy fields (voiceId, voiceAliases, modelId, outputFormat, apiKey) → talk.providers.elevenlabs (filled missing provider fields only).",
|
||||
]);
|
||||
});
|
||||
|
||||
it("normalizes talk provider ids without overriding explicit provider config", () => {
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { isDeepStrictEqual } from "node:util";
|
||||
import { migrateAmazonBedrockLegacyConfig } from "../../extensions/amazon-bedrock/config-api.js";
|
||||
import {
|
||||
ELEVENLABS_TALK_PROVIDER_ID,
|
||||
normalizeCompatibilityConfig as normalizeElevenLabsCompatibilityConfig,
|
||||
} from "../../extensions/elevenlabs/contract-api.js";
|
||||
import { migrateVoiceCallLegacyConfigInput } from "../../extensions/voice-call/config-api.js";
|
||||
import { normalizeProviderId } from "../agents/model-selection.js";
|
||||
import { shouldMoveSingleAccountChannelKey } from "../channels/plugins/setup-helpers.js";
|
||||
@@ -14,6 +10,7 @@ import { migrateLegacyWebSearchConfig } from "../config/legacy-web-search.js";
|
||||
import { migrateLegacyXSearchConfig } from "../config/legacy-x-search.js";
|
||||
import { normalizeTalkSection } from "../config/talk.js";
|
||||
import { DEFAULT_GOOGLE_API_BASE_URL } from "../infra/google-api-base-url.js";
|
||||
import { normalizeCompatibilityConfig as normalizeElevenLabsCompatibilityConfig } from "../plugin-sdk/elevenlabs.js";
|
||||
import { DEFAULT_ACCOUNT_ID } from "../routing/session-key.js";
|
||||
|
||||
export function normalizeCompatibilityConfigValues(cfg: OpenClawConfig): {
|
||||
@@ -409,20 +406,14 @@ export function normalizeCompatibilityConfigValues(cfg: OpenClawConfig): {
|
||||
return;
|
||||
}
|
||||
|
||||
const hasProviderShape = typeof rawTalk.provider === "string" || isRecord(rawTalk.providers);
|
||||
next = {
|
||||
...next,
|
||||
talk: normalizedTalk,
|
||||
};
|
||||
|
||||
if (hasProviderShape) {
|
||||
changes.push(
|
||||
"Normalized talk.provider/providers shape (trimmed provider ids and merged missing compatibility fields).",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
changes.push(`Moved legacy talk flat fields → talk.providers.${ELEVENLABS_TALK_PROVIDER_ID}.`);
|
||||
changes.push(
|
||||
"Normalized talk.provider/providers shape (trimmed provider ids and merged missing compatibility fields).",
|
||||
);
|
||||
};
|
||||
|
||||
const normalizeLegacyCrossContextMessageConfig = () => {
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { migrateElevenLabsLegacyTalkConfig } from "../../extensions/elevenlabs/contract-api.js";
|
||||
import {
|
||||
ELEVENLABS_TALK_LEGACY_CONFIG_RULES,
|
||||
migrateElevenLabsLegacyTalkConfig,
|
||||
} from "../plugin-sdk/elevenlabs.js";
|
||||
import {
|
||||
buildDefaultControlUiAllowedOrigins,
|
||||
hasConfiguredControlUiAllowedOrigins,
|
||||
@@ -140,16 +143,6 @@ function hasLegacyTtsProviderKeys(value: unknown): boolean {
|
||||
return LEGACY_TTS_PROVIDER_KEYS.some((key) => Object.prototype.hasOwnProperty.call(tts, key));
|
||||
}
|
||||
|
||||
function hasLegacyTalkFields(value: unknown): boolean {
|
||||
const talk = getRecord(value);
|
||||
if (!talk) {
|
||||
return false;
|
||||
}
|
||||
return ["voiceId", "voiceAliases", "modelId", "outputFormat", "apiKey"].some((key) =>
|
||||
Object.prototype.hasOwnProperty.call(talk, key),
|
||||
);
|
||||
}
|
||||
|
||||
function hasLegacySandboxPerSession(value: unknown): boolean {
|
||||
const sandbox = getRecord(value);
|
||||
return Boolean(sandbox && Object.prototype.hasOwnProperty.call(sandbox, "perSession"));
|
||||
@@ -163,9 +156,6 @@ function hasLegacyAgentListSandboxPerSession(value: unknown): boolean {
|
||||
}
|
||||
|
||||
function migrateLegacyTalkFields(raw: Record<string, unknown>, changes: string[]): void {
|
||||
if (!hasLegacyTalkFields(raw.talk)) {
|
||||
return;
|
||||
}
|
||||
const migrated = migrateElevenLabsLegacyTalkConfig(raw);
|
||||
if (migrated.changes.length === 0) {
|
||||
return;
|
||||
@@ -284,13 +274,6 @@ const LEGACY_TTS_RULES: LegacyConfigRule[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const TALK_RULE: LegacyConfigRule = {
|
||||
path: ["talk"],
|
||||
message:
|
||||
"talk.voiceId/talk.voiceAliases/talk.modelId/talk.outputFormat/talk.apiKey are legacy; use talk.providers.<provider> instead (auto-migrated on load).",
|
||||
match: (value) => hasLegacyTalkFields(value),
|
||||
};
|
||||
|
||||
const LEGACY_SANDBOX_SCOPE_RULES: LegacyConfigRule[] = [
|
||||
{
|
||||
path: ["agents", "defaults", "sandbox"],
|
||||
@@ -361,7 +344,7 @@ export const LEGACY_CONFIG_MIGRATIONS_RUNTIME: LegacyConfigMigrationSpec[] = [
|
||||
defineLegacyConfigMigration({
|
||||
id: "talk.legacy-fields->talk.providers",
|
||||
describe: "Move legacy Talk flat fields into talk.providers.<provider>",
|
||||
legacyRules: [TALK_RULE],
|
||||
legacyRules: ELEVENLABS_TALK_LEGACY_CONFIG_RULES,
|
||||
apply: (raw, changes) => {
|
||||
migrateLegacyTalkFields(raw, changes);
|
||||
},
|
||||
|
||||
@@ -17086,6 +17086,21 @@ export const GENERATED_BASE_CONFIG_SCHEMA: BaseConfigSchemaResponse = {
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
experimental: {
|
||||
type: "object",
|
||||
properties: {
|
||||
planTool: {
|
||||
type: "boolean",
|
||||
title: "Enable Structured Plan Tool",
|
||||
description:
|
||||
"Enable the experimental structured `update_plan` tool for non-trivial multi-step work tracking across all providers. OpenAI and OpenAI Codex runs auto-enable it even when this flag is unset.",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
title: "Experimental Tools",
|
||||
description:
|
||||
"Experimental built-in tool flags. Keep these off by default and enable only when you are intentionally testing a preview surface.",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
title: "Tools",
|
||||
@@ -20166,32 +20181,6 @@ export const GENERATED_BASE_CONFIG_SCHEMA: BaseConfigSchemaResponse = {
|
||||
additionalProperties: {
|
||||
type: "object",
|
||||
properties: {
|
||||
voiceId: {
|
||||
type: "string",
|
||||
title: "Talk Provider Voice ID",
|
||||
description: "Provider default voice ID for Talk mode.",
|
||||
},
|
||||
voiceAliases: {
|
||||
type: "object",
|
||||
propertyNames: {
|
||||
type: "string",
|
||||
},
|
||||
additionalProperties: {
|
||||
type: "string",
|
||||
},
|
||||
title: "Talk Provider Voice Aliases",
|
||||
description: "Optional provider voice alias map for Talk directives.",
|
||||
},
|
||||
modelId: {
|
||||
type: "string",
|
||||
title: "Talk Provider Model ID",
|
||||
description: "Provider default model ID for Talk mode.",
|
||||
},
|
||||
outputFormat: {
|
||||
type: "string",
|
||||
title: "Talk Provider Output Format",
|
||||
description: "Provider default output format for Talk mode.",
|
||||
},
|
||||
apiKey: {
|
||||
anyOf: [
|
||||
{
|
||||
@@ -20262,6 +20251,8 @@ export const GENERATED_BASE_CONFIG_SCHEMA: BaseConfigSchemaResponse = {
|
||||
},
|
||||
},
|
||||
additionalProperties: {},
|
||||
title: "Talk Provider Config",
|
||||
description: "Provider-owned Talk config fields for the matching provider id.",
|
||||
},
|
||||
title: "Talk Provider Settings",
|
||||
description:
|
||||
@@ -23466,6 +23457,16 @@ export const GENERATED_BASE_CONFIG_SCHEMA: BaseConfigSchemaResponse = {
|
||||
help: "Allowlist of target agent IDs permitted for agent_to_agent calls when orchestration is enabled. Use explicit allowlists to avoid uncontrolled cross-agent call graphs.",
|
||||
tags: ["access", "tools"],
|
||||
},
|
||||
"tools.experimental": {
|
||||
label: "Experimental Tools",
|
||||
help: "Experimental built-in tool flags. Keep these off by default and enable only when you are intentionally testing a preview surface.",
|
||||
tags: ["security", "tools", "advanced"],
|
||||
},
|
||||
"tools.experimental.planTool": {
|
||||
label: "Enable Structured Plan Tool",
|
||||
help: "Enable the experimental structured `update_plan` tool for non-trivial multi-step work tracking across all providers. OpenAI and OpenAI Codex runs auto-enable it even when this flag is unset.",
|
||||
tags: ["security", "tools", "advanced"],
|
||||
},
|
||||
"tools.elevated": {
|
||||
label: "Elevated Tool Access",
|
||||
help: "Elevated tool access controls for privileged command surfaces that should only be reachable from trusted senders. Keep disabled unless operator workflows explicitly require elevated actions.",
|
||||
@@ -26118,24 +26119,9 @@ export const GENERATED_BASE_CONFIG_SCHEMA: BaseConfigSchemaResponse = {
|
||||
help: "Provider-specific Talk settings keyed by provider id. During migration, prefer this over legacy talk.* keys.",
|
||||
tags: ["media"],
|
||||
},
|
||||
"talk.providers.*.voiceId": {
|
||||
label: "Talk Provider Voice ID",
|
||||
help: "Provider default voice ID for Talk mode.",
|
||||
tags: ["media"],
|
||||
},
|
||||
"talk.providers.*.voiceAliases": {
|
||||
label: "Talk Provider Voice Aliases",
|
||||
help: "Optional provider voice alias map for Talk directives.",
|
||||
tags: ["media"],
|
||||
},
|
||||
"talk.providers.*.modelId": {
|
||||
label: "Talk Provider Model ID",
|
||||
help: "Provider default model ID for Talk mode.",
|
||||
tags: ["models", "media"],
|
||||
},
|
||||
"talk.providers.*.outputFormat": {
|
||||
label: "Talk Provider Output Format",
|
||||
help: "Provider default output format for Talk mode.",
|
||||
"talk.providers.*": {
|
||||
label: "Talk Provider Config",
|
||||
help: "Provider-owned Talk config fields for the matching provider id.",
|
||||
tags: ["media"],
|
||||
},
|
||||
"talk.providers.*.apiKey": {
|
||||
|
||||
@@ -146,10 +146,7 @@ export const FIELD_HELP: Record<string, string> = {
|
||||
"talk.provider": 'Active Talk provider id (for example "elevenlabs").',
|
||||
"talk.providers":
|
||||
"Provider-specific Talk settings keyed by provider id. During migration, prefer this over legacy talk.* keys.",
|
||||
"talk.providers.*.voiceId": "Provider default voice ID for Talk mode.",
|
||||
"talk.providers.*.voiceAliases": "Optional provider voice alias map for Talk directives.",
|
||||
"talk.providers.*.modelId": "Provider default model ID for Talk mode.",
|
||||
"talk.providers.*.outputFormat": "Provider default output format for Talk mode.",
|
||||
"talk.providers.*": "Provider-owned Talk config fields for the matching provider id.",
|
||||
"talk.providers.*.apiKey": "Provider API key for Talk mode.", // pragma: allowlist secret
|
||||
"talk.interruptOnSpeech":
|
||||
"If true (default), stop assistant speech when the user starts speaking in Talk mode. Keep enabled for conversational turn-taking.",
|
||||
|
||||
@@ -743,10 +743,7 @@ export const FIELD_LABELS: Record<string, string> = {
|
||||
"messages.tts.providers.*.apiKey": "TTS Provider API Key", // pragma: allowlist secret
|
||||
"talk.provider": "Talk Active Provider",
|
||||
"talk.providers": "Talk Provider Settings",
|
||||
"talk.providers.*.voiceId": "Talk Provider Voice ID",
|
||||
"talk.providers.*.voiceAliases": "Talk Provider Voice Aliases",
|
||||
"talk.providers.*.modelId": "Talk Provider Model ID",
|
||||
"talk.providers.*.outputFormat": "Talk Provider Output Format",
|
||||
"talk.providers.*": "Talk Provider Config",
|
||||
"talk.providers.*.apiKey": "Talk Provider API Key", // pragma: allowlist secret
|
||||
channels: "Channels",
|
||||
"channels.defaults": "Channel Defaults",
|
||||
|
||||
@@ -20,7 +20,7 @@ async function withTempConfig(
|
||||
}
|
||||
|
||||
describe("talk normalization", () => {
|
||||
it("maps legacy ElevenLabs fields into provider/providers", () => {
|
||||
it("keeps core Talk normalization generic and ignores legacy provider-flat fields", () => {
|
||||
const normalized = normalizeTalkSection({
|
||||
voiceId: "voice-123",
|
||||
voiceAliases: { Clawd: "EXAVITQu4vr4xnSDxMaL" }, // pragma: allowlist secret
|
||||
@@ -32,15 +32,6 @@ describe("talk normalization", () => {
|
||||
} as unknown as never);
|
||||
|
||||
expect(normalized).toEqual({
|
||||
providers: {
|
||||
elevenlabs: {
|
||||
voiceId: "voice-123",
|
||||
voiceAliases: { Clawd: "EXAVITQu4vr4xnSDxMaL" },
|
||||
modelId: "eleven_v3",
|
||||
outputFormat: "pcm_44100",
|
||||
apiKey: "secret-key", // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
interruptOnSpeech: false,
|
||||
silenceTimeoutMs: 1500,
|
||||
});
|
||||
|
||||
@@ -7,8 +7,6 @@ import type {
|
||||
import type { OpenClawConfig } from "./types.js";
|
||||
import { coerceSecretRef } from "./types.secrets.js";
|
||||
|
||||
export const LEGACY_TALK_PROVIDER_ID = "elevenlabs";
|
||||
|
||||
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
@@ -21,20 +19,6 @@ function normalizeString(value: unknown): string | undefined {
|
||||
return trimmed.length > 0 ? trimmed : undefined;
|
||||
}
|
||||
|
||||
function normalizeVoiceAliases(value: unknown): Record<string, string> | undefined {
|
||||
if (!isPlainObject(value)) {
|
||||
return undefined;
|
||||
}
|
||||
const aliases: Record<string, string> = {};
|
||||
for (const [alias, rawId] of Object.entries(value)) {
|
||||
if (typeof rawId !== "string") {
|
||||
continue;
|
||||
}
|
||||
aliases[alias] = rawId;
|
||||
}
|
||||
return Object.keys(aliases).length > 0 ? aliases : undefined;
|
||||
}
|
||||
|
||||
function normalizeTalkSecretInput(value: unknown): TalkProviderConfig["apiKey"] | undefined {
|
||||
if (typeof value === "string") {
|
||||
const trimmed = value.trim();
|
||||
@@ -60,13 +44,6 @@ function normalizeTalkProviderConfig(value: unknown): TalkProviderConfig | undef
|
||||
if (raw === undefined) {
|
||||
continue;
|
||||
}
|
||||
if (key === "voiceAliases") {
|
||||
const aliases = normalizeVoiceAliases(raw);
|
||||
if (aliases) {
|
||||
provider.voiceAliases = aliases;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (key === "apiKey") {
|
||||
const normalized = normalizeTalkSecretInput(raw);
|
||||
if (normalized !== undefined) {
|
||||
@@ -74,13 +51,6 @@ function normalizeTalkProviderConfig(value: unknown): TalkProviderConfig | undef
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (key === "voiceId" || key === "modelId" || key === "outputFormat") {
|
||||
const normalized = normalizeString(raw);
|
||||
if (normalized) {
|
||||
provider[key] = normalized;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
provider[key] = raw;
|
||||
}
|
||||
|
||||
@@ -106,18 +76,6 @@ function normalizeTalkProviders(value: unknown): Record<string, TalkProviderConf
|
||||
return Object.keys(providers).length > 0 ? providers : undefined;
|
||||
}
|
||||
|
||||
function legacyProviderConfigFromTalk(
|
||||
source: Record<string, unknown>,
|
||||
): TalkProviderConfig | undefined {
|
||||
return normalizeTalkProviderConfig({
|
||||
voiceId: source.voiceId,
|
||||
voiceAliases: source.voiceAliases,
|
||||
modelId: source.modelId,
|
||||
outputFormat: source.outputFormat,
|
||||
apiKey: source.apiKey,
|
||||
});
|
||||
}
|
||||
|
||||
function activeProviderFromTalk(talk: TalkConfig): string | undefined {
|
||||
const provider = normalizeString(talk.provider);
|
||||
const providers = talk.providers;
|
||||
@@ -137,7 +95,6 @@ export function normalizeTalkSection(value: TalkConfig | undefined): TalkConfig
|
||||
}
|
||||
|
||||
const source = value as Record<string, unknown>;
|
||||
const hasNormalizedShape = typeof source.provider === "string" || isPlainObject(source.providers);
|
||||
const normalized: TalkConfig = {};
|
||||
if (typeof source.interruptOnSpeech === "boolean") {
|
||||
normalized.interruptOnSpeech = source.interruptOnSpeech;
|
||||
@@ -147,21 +104,13 @@ export function normalizeTalkSection(value: TalkConfig | undefined): TalkConfig
|
||||
normalized.silenceTimeoutMs = silenceTimeoutMs;
|
||||
}
|
||||
|
||||
if (hasNormalizedShape) {
|
||||
const providers = normalizeTalkProviders(source.providers);
|
||||
const provider = normalizeString(source.provider);
|
||||
if (providers) {
|
||||
normalized.providers = providers;
|
||||
}
|
||||
if (provider) {
|
||||
normalized.provider = provider;
|
||||
}
|
||||
return Object.keys(normalized).length > 0 ? normalized : undefined;
|
||||
const providers = normalizeTalkProviders(source.providers);
|
||||
const provider = normalizeString(source.provider);
|
||||
if (providers) {
|
||||
normalized.providers = providers;
|
||||
}
|
||||
|
||||
const legacyProviderConfig = legacyProviderConfigFromTalk(source);
|
||||
if (legacyProviderConfig) {
|
||||
normalized.providers = { [LEGACY_TALK_PROVIDER_ID]: legacyProviderConfig };
|
||||
if (provider) {
|
||||
normalized.provider = provider;
|
||||
}
|
||||
return Object.keys(normalized).length > 0 ? normalized : undefined;
|
||||
}
|
||||
|
||||
@@ -49,17 +49,9 @@ export type CanvasHostConfig = {
|
||||
};
|
||||
|
||||
export type TalkProviderConfig = {
|
||||
/** Default voice ID for the provider's Talk mode implementation. */
|
||||
voiceId?: string;
|
||||
/** Optional voice name -> provider voice ID map. */
|
||||
voiceAliases?: Record<string, string>;
|
||||
/** Default provider model ID for Talk mode. */
|
||||
modelId?: string;
|
||||
/** Default provider output format (for example pcm_44100). */
|
||||
outputFormat?: string;
|
||||
/** Provider API key (optional; provider-specific env fallback may apply). */
|
||||
apiKey?: SecretInput;
|
||||
/** Provider-specific extensions. */
|
||||
/** Provider-owned Talk config fields. */
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
|
||||
@@ -169,10 +169,6 @@ const PluginEntrySchema = z
|
||||
|
||||
const TalkProviderEntrySchema = z
|
||||
.object({
|
||||
voiceId: z.string().optional(),
|
||||
voiceAliases: z.record(z.string(), z.string()).optional(),
|
||||
modelId: z.string().optional(),
|
||||
outputFormat: z.string().optional(),
|
||||
apiKey: SecretInputSchema.optional().register(sensitive),
|
||||
})
|
||||
.catchall(z.unknown());
|
||||
|
||||
@@ -37,10 +37,6 @@ export const TalkSpeakParamsSchema = Type.Object(
|
||||
);
|
||||
|
||||
const talkProviderFieldSchemas = {
|
||||
voiceId: Type.Optional(Type.String()),
|
||||
voiceAliases: Type.Optional(Type.Record(Type.String(), Type.String())),
|
||||
modelId: Type.Optional(Type.String()),
|
||||
outputFormat: Type.Optional(Type.String()),
|
||||
apiKey: Type.Optional(SecretInputSchema),
|
||||
};
|
||||
|
||||
|
||||
@@ -52,6 +52,20 @@ function asRecord(value: unknown): Record<string, unknown> | undefined {
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function asStringRecord(value: unknown): Record<string, string> | undefined {
|
||||
const record = asRecord(value);
|
||||
if (!record) {
|
||||
return undefined;
|
||||
}
|
||||
const next: Record<string, string> = {};
|
||||
for (const [key, entryValue] of Object.entries(record)) {
|
||||
if (typeof entryValue === "string") {
|
||||
next[key] = entryValue;
|
||||
}
|
||||
}
|
||||
return Object.keys(next).length > 0 ? next : undefined;
|
||||
}
|
||||
|
||||
function normalizeAliasKey(value: string): string {
|
||||
return value.trim().toLowerCase();
|
||||
}
|
||||
@@ -63,7 +77,7 @@ function resolveTalkVoiceId(
|
||||
if (!requested) {
|
||||
return undefined;
|
||||
}
|
||||
const aliases = providerConfig.voiceAliases;
|
||||
const aliases = asStringRecord(providerConfig.voiceAliases);
|
||||
if (!aliases) {
|
||||
return requested;
|
||||
}
|
||||
|
||||
11
src/plugin-sdk/elevenlabs.ts
Normal file
11
src/plugin-sdk/elevenlabs.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
// Private helper surface for the bundled ElevenLabs speech plugin.
|
||||
// Keep this surface narrow and limited to config/doctor compatibility.
|
||||
|
||||
export {
|
||||
ELEVENLABS_TALK_PROVIDER_ID,
|
||||
ELEVENLABS_TALK_LEGACY_CONFIG_RULES,
|
||||
hasLegacyTalkFields,
|
||||
legacyConfigRules,
|
||||
migrateElevenLabsLegacyTalkConfig,
|
||||
normalizeCompatibilityConfig,
|
||||
} from "../../extensions/elevenlabs/contract-api.js";
|
||||
@@ -60,6 +60,10 @@ describe("config footprint guardrails", () => {
|
||||
"talk.modelId",
|
||||
"talk.outputFormat",
|
||||
"talk.apiKey",
|
||||
"talk.providers.*.voiceId",
|
||||
"talk.providers.*.voiceAliases",
|
||||
"talk.providers.*.modelId",
|
||||
"talk.providers.*.outputFormat",
|
||||
"agents.defaults.sandbox.perSession",
|
||||
"hooks.internal.handlers",
|
||||
"channels.telegram.groupMentionsOnly",
|
||||
|
||||
Reference in New Issue
Block a user