refactor: plugin-own speech provider config

This commit is contained in:
Peter Steinberger
2026-03-26 22:27:17 +00:00
parent 8eeb7f0829
commit 2c6d099b01
28 changed files with 1791 additions and 1229 deletions

View File

@@ -244,7 +244,7 @@
"exportName": "CliBackendPlugin",
"kind": "type",
"source": {
"line": 1292,
"line": 1305,
"path": "src/plugins/types.ts"
}
},
@@ -388,7 +388,7 @@
"exportName": "MediaUnderstandingProviderPlugin",
"kind": "type",
"source": {
"line": 951,
"line": 964,
"path": "src/plugins/types.ts"
}
},
@@ -406,7 +406,7 @@
"exportName": "OpenClawPluginApi",
"kind": "type",
"source": {
"line": 1336,
"line": 1349,
"path": "src/plugins/types.ts"
}
},
@@ -415,7 +415,7 @@
"exportName": "OpenClawPluginConfigSchema",
"kind": "type",
"source": {
"line": 88,
"line": 94,
"path": "src/plugins/types.ts"
}
},
@@ -424,7 +424,7 @@
"exportName": "PluginLogger",
"kind": "type",
"source": {
"line": 59,
"line": 65,
"path": "src/plugins/types.ts"
}
},
@@ -442,7 +442,7 @@
"exportName": "ProviderAuthContext",
"kind": "type",
"source": {
"line": 155,
"line": 161,
"path": "src/plugins/types.ts"
}
},
@@ -451,7 +451,7 @@
"exportName": "ProviderAuthResult",
"kind": "type",
"source": {
"line": 140,
"line": 146,
"path": "src/plugins/types.ts"
}
},
@@ -460,7 +460,7 @@
"exportName": "ProviderRuntimeModel",
"kind": "type",
"source": {
"line": 295,
"line": 301,
"path": "src/plugins/types.ts"
}
},
@@ -514,7 +514,7 @@
"exportName": "SpeechProviderPlugin",
"kind": "type",
"source": {
"line": 933,
"line": 939,
"path": "src/plugins/types.ts"
}
},
@@ -3405,7 +3405,7 @@
"exportName": "MediaUnderstandingProviderPlugin",
"kind": "type",
"source": {
"line": 951,
"line": 964,
"path": "src/plugins/types.ts"
}
},
@@ -3423,7 +3423,7 @@
"exportName": "OpenClawPluginApi",
"kind": "type",
"source": {
"line": 1336,
"line": 1349,
"path": "src/plugins/types.ts"
}
},
@@ -3432,7 +3432,7 @@
"exportName": "OpenClawPluginCommandDefinition",
"kind": "type",
"source": {
"line": 1068,
"line": 1081,
"path": "src/plugins/types.ts"
}
},
@@ -3441,7 +3441,7 @@
"exportName": "OpenClawPluginConfigSchema",
"kind": "type",
"source": {
"line": 88,
"line": 94,
"path": "src/plugins/types.ts"
}
},
@@ -3450,7 +3450,7 @@
"exportName": "OpenClawPluginDefinition",
"kind": "type",
"source": {
"line": 1318,
"line": 1331,
"path": "src/plugins/types.ts"
}
},
@@ -3459,7 +3459,7 @@
"exportName": "OpenClawPluginService",
"kind": "type",
"source": {
"line": 1285,
"line": 1298,
"path": "src/plugins/types.ts"
}
},
@@ -3468,7 +3468,7 @@
"exportName": "OpenClawPluginServiceContext",
"kind": "type",
"source": {
"line": 1277,
"line": 1290,
"path": "src/plugins/types.ts"
}
},
@@ -3477,7 +3477,7 @@
"exportName": "OpenClawPluginToolContext",
"kind": "type",
"source": {
"line": 103,
"line": 109,
"path": "src/plugins/types.ts"
}
},
@@ -3486,7 +3486,7 @@
"exportName": "OpenClawPluginToolFactory",
"kind": "type",
"source": {
"line": 120,
"line": 126,
"path": "src/plugins/types.ts"
}
},
@@ -3495,7 +3495,7 @@
"exportName": "PluginCommandContext",
"kind": "type",
"source": {
"line": 966,
"line": 979,
"path": "src/plugins/types.ts"
}
},
@@ -3504,7 +3504,7 @@
"exportName": "PluginInteractiveTelegramHandlerContext",
"kind": "type",
"source": {
"line": 1097,
"line": 1110,
"path": "src/plugins/types.ts"
}
},
@@ -3513,7 +3513,7 @@
"exportName": "PluginLogger",
"kind": "type",
"source": {
"line": 59,
"line": 65,
"path": "src/plugins/types.ts"
}
},
@@ -3531,7 +3531,7 @@
"exportName": "ProviderAugmentModelCatalogContext",
"kind": "type",
"source": {
"line": 571,
"line": 577,
"path": "src/plugins/types.ts"
}
},
@@ -3540,7 +3540,7 @@
"exportName": "ProviderAuthContext",
"kind": "type",
"source": {
"line": 155,
"line": 161,
"path": "src/plugins/types.ts"
}
},
@@ -3549,7 +3549,7 @@
"exportName": "ProviderAuthDoctorHintContext",
"kind": "type",
"source": {
"line": 446,
"line": 452,
"path": "src/plugins/types.ts"
}
},
@@ -3558,7 +3558,7 @@
"exportName": "ProviderAuthMethod",
"kind": "type",
"source": {
"line": 233,
"line": 239,
"path": "src/plugins/types.ts"
}
},
@@ -3567,7 +3567,7 @@
"exportName": "ProviderAuthMethodNonInteractiveContext",
"kind": "type",
"source": {
"line": 217,
"line": 223,
"path": "src/plugins/types.ts"
}
},
@@ -3576,7 +3576,7 @@
"exportName": "ProviderAuthResult",
"kind": "type",
"source": {
"line": 140,
"line": 146,
"path": "src/plugins/types.ts"
}
},
@@ -3585,7 +3585,7 @@
"exportName": "ProviderBuildMissingAuthMessageContext",
"kind": "type",
"source": {
"line": 499,
"line": 505,
"path": "src/plugins/types.ts"
}
},
@@ -3594,7 +3594,7 @@
"exportName": "ProviderBuiltInModelSuppressionContext",
"kind": "type",
"source": {
"line": 515,
"line": 521,
"path": "src/plugins/types.ts"
}
},
@@ -3603,7 +3603,7 @@
"exportName": "ProviderBuiltInModelSuppressionResult",
"kind": "type",
"source": {
"line": 524,
"line": 530,
"path": "src/plugins/types.ts"
}
},
@@ -3612,7 +3612,7 @@
"exportName": "ProviderCacheTtlEligibilityContext",
"kind": "type",
"source": {
"line": 487,
"line": 493,
"path": "src/plugins/types.ts"
}
},
@@ -3621,7 +3621,7 @@
"exportName": "ProviderCatalogContext",
"kind": "type",
"source": {
"line": 254,
"line": 260,
"path": "src/plugins/types.ts"
}
},
@@ -3630,7 +3630,7 @@
"exportName": "ProviderCatalogResult",
"kind": "type",
"source": {
"line": 277,
"line": 283,
"path": "src/plugins/types.ts"
}
},
@@ -3639,7 +3639,7 @@
"exportName": "ProviderDefaultThinkingPolicyContext",
"kind": "type",
"source": {
"line": 548,
"line": 554,
"path": "src/plugins/types.ts"
}
},
@@ -3648,7 +3648,7 @@
"exportName": "ProviderDiscoveryContext",
"kind": "type",
"source": {
"line": 587,
"line": 593,
"path": "src/plugins/types.ts"
}
},
@@ -3657,7 +3657,7 @@
"exportName": "ProviderFetchUsageSnapshotContext",
"kind": "type",
"source": {
"line": 427,
"line": 433,
"path": "src/plugins/types.ts"
}
},
@@ -3666,7 +3666,7 @@
"exportName": "ProviderModernModelPolicyContext",
"kind": "type",
"source": {
"line": 558,
"line": 564,
"path": "src/plugins/types.ts"
}
},
@@ -3675,7 +3675,7 @@
"exportName": "ProviderNormalizeResolvedModelContext",
"kind": "type",
"source": {
"line": 338,
"line": 344,
"path": "src/plugins/types.ts"
}
},
@@ -3684,7 +3684,7 @@
"exportName": "ProviderPreparedRuntimeAuth",
"kind": "type",
"source": {
"line": 374,
"line": 380,
"path": "src/plugins/types.ts"
}
},
@@ -3693,7 +3693,7 @@
"exportName": "ProviderPrepareDynamicModelContext",
"kind": "type",
"source": {
"line": 329,
"line": 335,
"path": "src/plugins/types.ts"
}
},
@@ -3702,7 +3702,7 @@
"exportName": "ProviderPrepareExtraParamsContext",
"kind": "type",
"source": {
"line": 460,
"line": 466,
"path": "src/plugins/types.ts"
}
},
@@ -3711,7 +3711,7 @@
"exportName": "ProviderPrepareRuntimeAuthContext",
"kind": "type",
"source": {
"line": 353,
"line": 359,
"path": "src/plugins/types.ts"
}
},
@@ -3720,7 +3720,7 @@
"exportName": "ProviderResolvedUsageAuth",
"kind": "type",
"source": {
"line": 414,
"line": 420,
"path": "src/plugins/types.ts"
}
},
@@ -3729,7 +3729,7 @@
"exportName": "ProviderResolveDynamicModelContext",
"kind": "type",
"source": {
"line": 312,
"line": 318,
"path": "src/plugins/types.ts"
}
},
@@ -3738,7 +3738,7 @@
"exportName": "ProviderResolveUsageAuthContext",
"kind": "type",
"source": {
"line": 395,
"line": 401,
"path": "src/plugins/types.ts"
}
},
@@ -3747,7 +3747,7 @@
"exportName": "ProviderRuntimeModel",
"kind": "type",
"source": {
"line": 295,
"line": 301,
"path": "src/plugins/types.ts"
}
},
@@ -3756,7 +3756,7 @@
"exportName": "ProviderThinkingPolicyContext",
"kind": "type",
"source": {
"line": 536,
"line": 542,
"path": "src/plugins/types.ts"
}
},
@@ -3774,7 +3774,7 @@
"exportName": "ProviderWrapStreamFnContext",
"kind": "type",
"source": {
"line": 477,
"line": 483,
"path": "src/plugins/types.ts"
}
},
@@ -3819,7 +3819,7 @@
"exportName": "SpeechProviderPlugin",
"kind": "type",
"source": {
"line": 933,
"line": 939,
"path": "src/plugins/types.ts"
}
},
@@ -3911,7 +3911,7 @@
"exportName": "MediaUnderstandingProviderPlugin",
"kind": "type",
"source": {
"line": 951,
"line": 964,
"path": "src/plugins/types.ts"
}
},
@@ -3929,7 +3929,7 @@
"exportName": "OpenClawPluginApi",
"kind": "type",
"source": {
"line": 1336,
"line": 1349,
"path": "src/plugins/types.ts"
}
},
@@ -3938,7 +3938,7 @@
"exportName": "OpenClawPluginCommandDefinition",
"kind": "type",
"source": {
"line": 1068,
"line": 1081,
"path": "src/plugins/types.ts"
}
},
@@ -3947,7 +3947,7 @@
"exportName": "OpenClawPluginConfigSchema",
"kind": "type",
"source": {
"line": 88,
"line": 94,
"path": "src/plugins/types.ts"
}
},
@@ -3956,7 +3956,7 @@
"exportName": "OpenClawPluginDefinition",
"kind": "type",
"source": {
"line": 1318,
"line": 1331,
"path": "src/plugins/types.ts"
}
},
@@ -3965,7 +3965,7 @@
"exportName": "OpenClawPluginService",
"kind": "type",
"source": {
"line": 1285,
"line": 1298,
"path": "src/plugins/types.ts"
}
},
@@ -3974,7 +3974,7 @@
"exportName": "OpenClawPluginServiceContext",
"kind": "type",
"source": {
"line": 1277,
"line": 1290,
"path": "src/plugins/types.ts"
}
},
@@ -3983,7 +3983,7 @@
"exportName": "PluginCommandContext",
"kind": "type",
"source": {
"line": 966,
"line": 979,
"path": "src/plugins/types.ts"
}
},
@@ -3992,7 +3992,7 @@
"exportName": "PluginInteractiveTelegramHandlerContext",
"kind": "type",
"source": {
"line": 1097,
"line": 1110,
"path": "src/plugins/types.ts"
}
},
@@ -4001,7 +4001,7 @@
"exportName": "PluginLogger",
"kind": "type",
"source": {
"line": 59,
"line": 65,
"path": "src/plugins/types.ts"
}
},
@@ -4010,7 +4010,7 @@
"exportName": "ProviderAugmentModelCatalogContext",
"kind": "type",
"source": {
"line": 571,
"line": 577,
"path": "src/plugins/types.ts"
}
},
@@ -4019,7 +4019,7 @@
"exportName": "ProviderAuthContext",
"kind": "type",
"source": {
"line": 155,
"line": 161,
"path": "src/plugins/types.ts"
}
},
@@ -4028,7 +4028,7 @@
"exportName": "ProviderAuthDoctorHintContext",
"kind": "type",
"source": {
"line": 446,
"line": 452,
"path": "src/plugins/types.ts"
}
},
@@ -4037,7 +4037,7 @@
"exportName": "ProviderAuthMethod",
"kind": "type",
"source": {
"line": 233,
"line": 239,
"path": "src/plugins/types.ts"
}
},
@@ -4046,7 +4046,7 @@
"exportName": "ProviderAuthMethodNonInteractiveContext",
"kind": "type",
"source": {
"line": 217,
"line": 223,
"path": "src/plugins/types.ts"
}
},
@@ -4055,7 +4055,7 @@
"exportName": "ProviderAuthResult",
"kind": "type",
"source": {
"line": 140,
"line": 146,
"path": "src/plugins/types.ts"
}
},
@@ -4064,7 +4064,7 @@
"exportName": "ProviderBuildMissingAuthMessageContext",
"kind": "type",
"source": {
"line": 499,
"line": 505,
"path": "src/plugins/types.ts"
}
},
@@ -4073,7 +4073,7 @@
"exportName": "ProviderBuiltInModelSuppressionContext",
"kind": "type",
"source": {
"line": 515,
"line": 521,
"path": "src/plugins/types.ts"
}
},
@@ -4082,7 +4082,7 @@
"exportName": "ProviderBuiltInModelSuppressionResult",
"kind": "type",
"source": {
"line": 524,
"line": 530,
"path": "src/plugins/types.ts"
}
},
@@ -4091,7 +4091,7 @@
"exportName": "ProviderCacheTtlEligibilityContext",
"kind": "type",
"source": {
"line": 487,
"line": 493,
"path": "src/plugins/types.ts"
}
},
@@ -4100,7 +4100,7 @@
"exportName": "ProviderCatalogContext",
"kind": "type",
"source": {
"line": 254,
"line": 260,
"path": "src/plugins/types.ts"
}
},
@@ -4109,7 +4109,7 @@
"exportName": "ProviderCatalogResult",
"kind": "type",
"source": {
"line": 277,
"line": 283,
"path": "src/plugins/types.ts"
}
},
@@ -4118,7 +4118,7 @@
"exportName": "ProviderDefaultThinkingPolicyContext",
"kind": "type",
"source": {
"line": 548,
"line": 554,
"path": "src/plugins/types.ts"
}
},
@@ -4127,7 +4127,7 @@
"exportName": "ProviderDiscoveryContext",
"kind": "type",
"source": {
"line": 587,
"line": 593,
"path": "src/plugins/types.ts"
}
},
@@ -4136,7 +4136,7 @@
"exportName": "ProviderFetchUsageSnapshotContext",
"kind": "type",
"source": {
"line": 427,
"line": 433,
"path": "src/plugins/types.ts"
}
},
@@ -4145,7 +4145,7 @@
"exportName": "ProviderModernModelPolicyContext",
"kind": "type",
"source": {
"line": 558,
"line": 564,
"path": "src/plugins/types.ts"
}
},
@@ -4154,7 +4154,7 @@
"exportName": "ProviderNormalizeResolvedModelContext",
"kind": "type",
"source": {
"line": 338,
"line": 344,
"path": "src/plugins/types.ts"
}
},
@@ -4163,7 +4163,7 @@
"exportName": "ProviderPreparedRuntimeAuth",
"kind": "type",
"source": {
"line": 374,
"line": 380,
"path": "src/plugins/types.ts"
}
},
@@ -4172,7 +4172,7 @@
"exportName": "ProviderPrepareDynamicModelContext",
"kind": "type",
"source": {
"line": 329,
"line": 335,
"path": "src/plugins/types.ts"
}
},
@@ -4181,7 +4181,7 @@
"exportName": "ProviderPrepareExtraParamsContext",
"kind": "type",
"source": {
"line": 460,
"line": 466,
"path": "src/plugins/types.ts"
}
},
@@ -4190,7 +4190,7 @@
"exportName": "ProviderPrepareRuntimeAuthContext",
"kind": "type",
"source": {
"line": 353,
"line": 359,
"path": "src/plugins/types.ts"
}
},
@@ -4199,7 +4199,7 @@
"exportName": "ProviderResolvedUsageAuth",
"kind": "type",
"source": {
"line": 414,
"line": 420,
"path": "src/plugins/types.ts"
}
},
@@ -4208,7 +4208,7 @@
"exportName": "ProviderResolveDynamicModelContext",
"kind": "type",
"source": {
"line": 312,
"line": 318,
"path": "src/plugins/types.ts"
}
},
@@ -4217,7 +4217,7 @@
"exportName": "ProviderResolveUsageAuthContext",
"kind": "type",
"source": {
"line": 395,
"line": 401,
"path": "src/plugins/types.ts"
}
},
@@ -4226,7 +4226,7 @@
"exportName": "ProviderRuntimeModel",
"kind": "type",
"source": {
"line": 295,
"line": 301,
"path": "src/plugins/types.ts"
}
},
@@ -4235,7 +4235,7 @@
"exportName": "ProviderThinkingPolicyContext",
"kind": "type",
"source": {
"line": 536,
"line": 542,
"path": "src/plugins/types.ts"
}
},
@@ -4244,7 +4244,7 @@
"exportName": "ProviderWrapStreamFnContext",
"kind": "type",
"source": {
"line": 477,
"line": 483,
"path": "src/plugins/types.ts"
}
},
@@ -4253,7 +4253,7 @@
"exportName": "SpeechProviderPlugin",
"kind": "type",
"source": {
"line": 933,
"line": 939,
"path": "src/plugins/types.ts"
}
}

View File

@@ -25,7 +25,7 @@
{"declaration":"export type ChannelStatusIssue = ChannelStatusIssue;","entrypoint":"index","exportName":"ChannelStatusIssue","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":100,"sourcePath":"src/channels/plugins/types.core.ts"}
{"declaration":"export type OpenClawConfig = OpenClawConfig;","entrypoint":"index","exportName":"ClawdbotConfig","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":32,"sourcePath":"src/config/types.openclaw.ts"}
{"declaration":"export type CliBackendConfig = CliBackendConfig;","entrypoint":"index","exportName":"CliBackendConfig","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":47,"sourcePath":"src/config/types.agent-defaults.ts"}
{"declaration":"export type CliBackendPlugin = CliBackendPlugin;","entrypoint":"index","exportName":"CliBackendPlugin","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":1292,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type CliBackendPlugin = CliBackendPlugin;","entrypoint":"index","exportName":"CliBackendPlugin","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":1305,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type CompiledConfiguredBinding = CompiledConfiguredBinding;","entrypoint":"index","exportName":"CompiledConfiguredBinding","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":38,"sourcePath":"src/channels/plugins/binding-types.ts"}
{"declaration":"export type ConfiguredBindingConversation = ConversationRef;","entrypoint":"index","exportName":"ConfiguredBindingConversation","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":13,"sourcePath":"src/channels/plugins/binding-types.ts"}
{"declaration":"export type ConfiguredBindingResolution = ConfiguredBindingResolution;","entrypoint":"index","exportName":"ConfiguredBindingResolution","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":49,"sourcePath":"src/channels/plugins/binding-types.ts"}
@@ -41,21 +41,21 @@
{"declaration":"export type ImageGenerationResolution = ImageGenerationResolution;","entrypoint":"index","exportName":"ImageGenerationResolution","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":12,"sourcePath":"src/image-generation/types.ts"}
{"declaration":"export type ImageGenerationResult = ImageGenerationResult;","entrypoint":"index","exportName":"ImageGenerationResult","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":36,"sourcePath":"src/image-generation/types.ts"}
{"declaration":"export type ImageGenerationSourceImage = ImageGenerationSourceImage;","entrypoint":"index","exportName":"ImageGenerationSourceImage","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":14,"sourcePath":"src/image-generation/types.ts"}
{"declaration":"export type MediaUnderstandingProviderPlugin = MediaUnderstandingProvider;","entrypoint":"index","exportName":"MediaUnderstandingProviderPlugin","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":951,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type MediaUnderstandingProviderPlugin = MediaUnderstandingProvider;","entrypoint":"index","exportName":"MediaUnderstandingProviderPlugin","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":964,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type OpenClawConfig = OpenClawConfig;","entrypoint":"index","exportName":"OpenClawConfig","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":32,"sourcePath":"src/config/types.openclaw.ts"}
{"declaration":"export type OpenClawPluginApi = OpenClawPluginApi;","entrypoint":"index","exportName":"OpenClawPluginApi","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":1336,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type OpenClawPluginConfigSchema = OpenClawPluginConfigSchema;","entrypoint":"index","exportName":"OpenClawPluginConfigSchema","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":88,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type PluginLogger = PluginLogger;","entrypoint":"index","exportName":"PluginLogger","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":59,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type OpenClawPluginApi = OpenClawPluginApi;","entrypoint":"index","exportName":"OpenClawPluginApi","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":1349,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type OpenClawPluginConfigSchema = OpenClawPluginConfigSchema;","entrypoint":"index","exportName":"OpenClawPluginConfigSchema","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":94,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type PluginLogger = PluginLogger;","entrypoint":"index","exportName":"PluginLogger","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":65,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type PluginRuntime = PluginRuntime;","entrypoint":"index","exportName":"PluginRuntime","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":54,"sourcePath":"src/plugins/runtime/types.ts"}
{"declaration":"export type ProviderAuthContext = ProviderAuthContext;","entrypoint":"index","exportName":"ProviderAuthContext","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":155,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderAuthResult = ProviderAuthResult;","entrypoint":"index","exportName":"ProviderAuthResult","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":140,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderRuntimeModel = ProviderRuntimeModel;","entrypoint":"index","exportName":"ProviderRuntimeModel","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":295,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderAuthContext = ProviderAuthContext;","entrypoint":"index","exportName":"ProviderAuthContext","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":161,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderAuthResult = ProviderAuthResult;","entrypoint":"index","exportName":"ProviderAuthResult","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":146,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderRuntimeModel = ProviderRuntimeModel;","entrypoint":"index","exportName":"ProviderRuntimeModel","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":301,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ReplyPayload = ReplyPayload;","entrypoint":"index","exportName":"ReplyPayload","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":76,"sourcePath":"src/auto-reply/types.ts"}
{"declaration":"export type RuntimeEnv = RuntimeEnv;","entrypoint":"index","exportName":"RuntimeEnv","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":4,"sourcePath":"src/runtime.ts"}
{"declaration":"export type RuntimeLogger = RuntimeLogger;","entrypoint":"index","exportName":"RuntimeLogger","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":7,"sourcePath":"src/plugins/runtime/types-core.ts"}
{"declaration":"export type SecretInput = SecretInput;","entrypoint":"index","exportName":"SecretInput","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":16,"sourcePath":"src/config/types.secrets.ts"}
{"declaration":"export type SecretRef = SecretRef;","entrypoint":"index","exportName":"SecretRef","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":10,"sourcePath":"src/config/types.secrets.ts"}
{"declaration":"export type SpeechProviderPlugin = SpeechProviderPlugin;","entrypoint":"index","exportName":"SpeechProviderPlugin","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":933,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type SpeechProviderPlugin = SpeechProviderPlugin;","entrypoint":"index","exportName":"SpeechProviderPlugin","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":939,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type StatefulBindingTargetDescriptor = StatefulBindingTargetDescriptor;","entrypoint":"index","exportName":"StatefulBindingTargetDescriptor","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":17,"sourcePath":"src/channels/plugins/binding-types.ts"}
{"declaration":"export type StatefulBindingTargetDriver = StatefulBindingTargetDriver;","entrypoint":"index","exportName":"StatefulBindingTargetDriver","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":15,"sourcePath":"src/channels/plugins/stateful-target-drivers.ts"}
{"declaration":"export type StatefulBindingTargetReadyResult = StatefulBindingTargetReadyResult;","entrypoint":"index","exportName":"StatefulBindingTargetReadyResult","importSpecifier":"openclaw/plugin-sdk","kind":"type","recordType":"export","sourceLine":7,"sourcePath":"src/channels/plugins/stateful-target-drivers.ts"}
@@ -374,53 +374,53 @@
{"declaration":"export type ChannelPlugin = ChannelPlugin<ResolvedAccount, Probe, Audit>;","entrypoint":"core","exportName":"ChannelPlugin","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":55,"sourcePath":"src/channels/plugins/types.plugin.ts"}
{"declaration":"export type GatewayBindUrlResult = GatewayBindUrlResult;","entrypoint":"core","exportName":"GatewayBindUrlResult","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1,"sourcePath":"src/shared/gateway-bind-url.ts"}
{"declaration":"export type GatewayRequestHandlerOptions = GatewayRequestHandlerOptions;","entrypoint":"core","exportName":"GatewayRequestHandlerOptions","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":112,"sourcePath":"src/gateway/server-methods/types.ts"}
{"declaration":"export type MediaUnderstandingProviderPlugin = MediaUnderstandingProvider;","entrypoint":"core","exportName":"MediaUnderstandingProviderPlugin","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":951,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type MediaUnderstandingProviderPlugin = MediaUnderstandingProvider;","entrypoint":"core","exportName":"MediaUnderstandingProviderPlugin","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":964,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type OpenClawConfig = OpenClawConfig;","entrypoint":"core","exportName":"OpenClawConfig","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":32,"sourcePath":"src/config/types.openclaw.ts"}
{"declaration":"export type OpenClawPluginApi = OpenClawPluginApi;","entrypoint":"core","exportName":"OpenClawPluginApi","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1336,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type OpenClawPluginCommandDefinition = OpenClawPluginCommandDefinition;","entrypoint":"core","exportName":"OpenClawPluginCommandDefinition","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1068,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type OpenClawPluginConfigSchema = OpenClawPluginConfigSchema;","entrypoint":"core","exportName":"OpenClawPluginConfigSchema","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":88,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type OpenClawPluginDefinition = OpenClawPluginDefinition;","entrypoint":"core","exportName":"OpenClawPluginDefinition","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1318,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type OpenClawPluginService = OpenClawPluginService;","entrypoint":"core","exportName":"OpenClawPluginService","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1285,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type OpenClawPluginServiceContext = OpenClawPluginServiceContext;","entrypoint":"core","exportName":"OpenClawPluginServiceContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1277,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type OpenClawPluginToolContext = OpenClawPluginToolContext;","entrypoint":"core","exportName":"OpenClawPluginToolContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":103,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type OpenClawPluginToolFactory = OpenClawPluginToolFactory;","entrypoint":"core","exportName":"OpenClawPluginToolFactory","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":120,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type PluginCommandContext = PluginCommandContext;","entrypoint":"core","exportName":"PluginCommandContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":966,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type PluginInteractiveTelegramHandlerContext = PluginInteractiveTelegramHandlerContext;","entrypoint":"core","exportName":"PluginInteractiveTelegramHandlerContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1097,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type PluginLogger = PluginLogger;","entrypoint":"core","exportName":"PluginLogger","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":59,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type OpenClawPluginApi = OpenClawPluginApi;","entrypoint":"core","exportName":"OpenClawPluginApi","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1349,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type OpenClawPluginCommandDefinition = OpenClawPluginCommandDefinition;","entrypoint":"core","exportName":"OpenClawPluginCommandDefinition","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1081,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type OpenClawPluginConfigSchema = OpenClawPluginConfigSchema;","entrypoint":"core","exportName":"OpenClawPluginConfigSchema","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":94,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type OpenClawPluginDefinition = OpenClawPluginDefinition;","entrypoint":"core","exportName":"OpenClawPluginDefinition","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1331,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type OpenClawPluginService = OpenClawPluginService;","entrypoint":"core","exportName":"OpenClawPluginService","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1298,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type OpenClawPluginServiceContext = OpenClawPluginServiceContext;","entrypoint":"core","exportName":"OpenClawPluginServiceContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1290,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type OpenClawPluginToolContext = OpenClawPluginToolContext;","entrypoint":"core","exportName":"OpenClawPluginToolContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":109,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type OpenClawPluginToolFactory = OpenClawPluginToolFactory;","entrypoint":"core","exportName":"OpenClawPluginToolFactory","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":126,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type PluginCommandContext = PluginCommandContext;","entrypoint":"core","exportName":"PluginCommandContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":979,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type PluginInteractiveTelegramHandlerContext = PluginInteractiveTelegramHandlerContext;","entrypoint":"core","exportName":"PluginInteractiveTelegramHandlerContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1110,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type PluginLogger = PluginLogger;","entrypoint":"core","exportName":"PluginLogger","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":65,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type PluginRuntime = PluginRuntime;","entrypoint":"core","exportName":"PluginRuntime","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":54,"sourcePath":"src/plugins/runtime/types.ts"}
{"declaration":"export type ProviderAugmentModelCatalogContext = ProviderAugmentModelCatalogContext;","entrypoint":"core","exportName":"ProviderAugmentModelCatalogContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":571,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderAuthContext = ProviderAuthContext;","entrypoint":"core","exportName":"ProviderAuthContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":155,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderAuthDoctorHintContext = ProviderAuthDoctorHintContext;","entrypoint":"core","exportName":"ProviderAuthDoctorHintContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":446,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderAuthMethod = ProviderAuthMethod;","entrypoint":"core","exportName":"ProviderAuthMethod","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":233,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderAuthMethodNonInteractiveContext = ProviderAuthMethodNonInteractiveContext;","entrypoint":"core","exportName":"ProviderAuthMethodNonInteractiveContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":217,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderAuthResult = ProviderAuthResult;","entrypoint":"core","exportName":"ProviderAuthResult","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":140,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderBuildMissingAuthMessageContext = ProviderBuildMissingAuthMessageContext;","entrypoint":"core","exportName":"ProviderBuildMissingAuthMessageContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":499,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderBuiltInModelSuppressionContext = ProviderBuiltInModelSuppressionContext;","entrypoint":"core","exportName":"ProviderBuiltInModelSuppressionContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":515,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderBuiltInModelSuppressionResult = ProviderBuiltInModelSuppressionResult;","entrypoint":"core","exportName":"ProviderBuiltInModelSuppressionResult","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":524,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderCacheTtlEligibilityContext = ProviderCacheTtlEligibilityContext;","entrypoint":"core","exportName":"ProviderCacheTtlEligibilityContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":487,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderCatalogContext = ProviderCatalogContext;","entrypoint":"core","exportName":"ProviderCatalogContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":254,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderCatalogResult = ProviderCatalogResult;","entrypoint":"core","exportName":"ProviderCatalogResult","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":277,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderDefaultThinkingPolicyContext = ProviderDefaultThinkingPolicyContext;","entrypoint":"core","exportName":"ProviderDefaultThinkingPolicyContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":548,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderDiscoveryContext = ProviderCatalogContext;","entrypoint":"core","exportName":"ProviderDiscoveryContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":587,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderFetchUsageSnapshotContext = ProviderFetchUsageSnapshotContext;","entrypoint":"core","exportName":"ProviderFetchUsageSnapshotContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":427,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderModernModelPolicyContext = ProviderModernModelPolicyContext;","entrypoint":"core","exportName":"ProviderModernModelPolicyContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":558,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderNormalizeResolvedModelContext = ProviderNormalizeResolvedModelContext;","entrypoint":"core","exportName":"ProviderNormalizeResolvedModelContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":338,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderPreparedRuntimeAuth = ProviderPreparedRuntimeAuth;","entrypoint":"core","exportName":"ProviderPreparedRuntimeAuth","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":374,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderPrepareDynamicModelContext = ProviderResolveDynamicModelContext;","entrypoint":"core","exportName":"ProviderPrepareDynamicModelContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":329,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderPrepareExtraParamsContext = ProviderPrepareExtraParamsContext;","entrypoint":"core","exportName":"ProviderPrepareExtraParamsContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":460,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderPrepareRuntimeAuthContext = ProviderPrepareRuntimeAuthContext;","entrypoint":"core","exportName":"ProviderPrepareRuntimeAuthContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":353,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderResolvedUsageAuth = ProviderResolvedUsageAuth;","entrypoint":"core","exportName":"ProviderResolvedUsageAuth","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":414,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderResolveDynamicModelContext = ProviderResolveDynamicModelContext;","entrypoint":"core","exportName":"ProviderResolveDynamicModelContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":312,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderResolveUsageAuthContext = ProviderResolveUsageAuthContext;","entrypoint":"core","exportName":"ProviderResolveUsageAuthContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":395,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderRuntimeModel = ProviderRuntimeModel;","entrypoint":"core","exportName":"ProviderRuntimeModel","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":295,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderThinkingPolicyContext = ProviderThinkingPolicyContext;","entrypoint":"core","exportName":"ProviderThinkingPolicyContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":536,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderAugmentModelCatalogContext = ProviderAugmentModelCatalogContext;","entrypoint":"core","exportName":"ProviderAugmentModelCatalogContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":577,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderAuthContext = ProviderAuthContext;","entrypoint":"core","exportName":"ProviderAuthContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":161,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderAuthDoctorHintContext = ProviderAuthDoctorHintContext;","entrypoint":"core","exportName":"ProviderAuthDoctorHintContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":452,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderAuthMethod = ProviderAuthMethod;","entrypoint":"core","exportName":"ProviderAuthMethod","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":239,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderAuthMethodNonInteractiveContext = ProviderAuthMethodNonInteractiveContext;","entrypoint":"core","exportName":"ProviderAuthMethodNonInteractiveContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":223,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderAuthResult = ProviderAuthResult;","entrypoint":"core","exportName":"ProviderAuthResult","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":146,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderBuildMissingAuthMessageContext = ProviderBuildMissingAuthMessageContext;","entrypoint":"core","exportName":"ProviderBuildMissingAuthMessageContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":505,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderBuiltInModelSuppressionContext = ProviderBuiltInModelSuppressionContext;","entrypoint":"core","exportName":"ProviderBuiltInModelSuppressionContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":521,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderBuiltInModelSuppressionResult = ProviderBuiltInModelSuppressionResult;","entrypoint":"core","exportName":"ProviderBuiltInModelSuppressionResult","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":530,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderCacheTtlEligibilityContext = ProviderCacheTtlEligibilityContext;","entrypoint":"core","exportName":"ProviderCacheTtlEligibilityContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":493,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderCatalogContext = ProviderCatalogContext;","entrypoint":"core","exportName":"ProviderCatalogContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":260,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderCatalogResult = ProviderCatalogResult;","entrypoint":"core","exportName":"ProviderCatalogResult","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":283,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderDefaultThinkingPolicyContext = ProviderDefaultThinkingPolicyContext;","entrypoint":"core","exportName":"ProviderDefaultThinkingPolicyContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":554,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderDiscoveryContext = ProviderCatalogContext;","entrypoint":"core","exportName":"ProviderDiscoveryContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":593,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderFetchUsageSnapshotContext = ProviderFetchUsageSnapshotContext;","entrypoint":"core","exportName":"ProviderFetchUsageSnapshotContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":433,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderModernModelPolicyContext = ProviderModernModelPolicyContext;","entrypoint":"core","exportName":"ProviderModernModelPolicyContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":564,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderNormalizeResolvedModelContext = ProviderNormalizeResolvedModelContext;","entrypoint":"core","exportName":"ProviderNormalizeResolvedModelContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":344,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderPreparedRuntimeAuth = ProviderPreparedRuntimeAuth;","entrypoint":"core","exportName":"ProviderPreparedRuntimeAuth","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":380,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderPrepareDynamicModelContext = ProviderResolveDynamicModelContext;","entrypoint":"core","exportName":"ProviderPrepareDynamicModelContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":335,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderPrepareExtraParamsContext = ProviderPrepareExtraParamsContext;","entrypoint":"core","exportName":"ProviderPrepareExtraParamsContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":466,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderPrepareRuntimeAuthContext = ProviderPrepareRuntimeAuthContext;","entrypoint":"core","exportName":"ProviderPrepareRuntimeAuthContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":359,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderResolvedUsageAuth = ProviderResolvedUsageAuth;","entrypoint":"core","exportName":"ProviderResolvedUsageAuth","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":420,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderResolveDynamicModelContext = ProviderResolveDynamicModelContext;","entrypoint":"core","exportName":"ProviderResolveDynamicModelContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":318,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderResolveUsageAuthContext = ProviderResolveUsageAuthContext;","entrypoint":"core","exportName":"ProviderResolveUsageAuthContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":401,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderRuntimeModel = ProviderRuntimeModel;","entrypoint":"core","exportName":"ProviderRuntimeModel","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":301,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderThinkingPolicyContext = ProviderThinkingPolicyContext;","entrypoint":"core","exportName":"ProviderThinkingPolicyContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":542,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderUsageSnapshot = ProviderUsageSnapshot;","entrypoint":"core","exportName":"ProviderUsageSnapshot","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":7,"sourcePath":"src/infra/provider-usage.types.ts"}
{"declaration":"export type ProviderWrapStreamFnContext = ProviderWrapStreamFnContext;","entrypoint":"core","exportName":"ProviderWrapStreamFnContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":477,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderWrapStreamFnContext = ProviderWrapStreamFnContext;","entrypoint":"core","exportName":"ProviderWrapStreamFnContext","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":483,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type RoutePeer = RoutePeer;","entrypoint":"core","exportName":"RoutePeer","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":21,"sourcePath":"src/routing/resolve-route.ts"}
{"declaration":"export type RoutePeerKind = ChatType;","entrypoint":"core","exportName":"RoutePeerKind","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":19,"sourcePath":"src/routing/resolve-route.ts"}
{"declaration":"export type SecretFileReadOptions = SecretFileReadOptions;","entrypoint":"core","exportName":"SecretFileReadOptions","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":7,"sourcePath":"src/infra/secret-file.ts"}
{"declaration":"export type SecretFileReadResult = SecretFileReadResult;","entrypoint":"core","exportName":"SecretFileReadResult","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":12,"sourcePath":"src/infra/secret-file.ts"}
{"declaration":"export type SpeechProviderPlugin = SpeechProviderPlugin;","entrypoint":"core","exportName":"SpeechProviderPlugin","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":933,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type SpeechProviderPlugin = SpeechProviderPlugin;","entrypoint":"core","exportName":"SpeechProviderPlugin","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":939,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type TailscaleStatusCommandResult = TailscaleStatusCommandResult;","entrypoint":"core","exportName":"TailscaleStatusCommandResult","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":1,"sourcePath":"src/shared/tailscale-status.ts"}
{"declaration":"export type TailscaleStatusCommandRunner = TailscaleStatusCommandRunner;","entrypoint":"core","exportName":"TailscaleStatusCommandRunner","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":6,"sourcePath":"src/shared/tailscale-status.ts"}
{"declaration":"export type UsageProviderId = UsageProviderId;","entrypoint":"core","exportName":"UsageProviderId","importSpecifier":"openclaw/plugin-sdk/core","kind":"type","recordType":"export","sourceLine":20,"sourcePath":"src/infra/provider-usage.types.ts"}
@@ -430,45 +430,45 @@
{"declaration":"export function definePluginEntry({ id, name, description, kind, configSchema, register, }: DefinePluginEntryOptions): DefinedPluginEntry;","entrypoint":"plugin-entry","exportName":"definePluginEntry","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"function","recordType":"export","sourceLine":88,"sourcePath":"src/plugin-sdk/plugin-entry.ts"}
{"declaration":"export function emptyPluginConfigSchema(): OpenClawPluginConfigSchema;","entrypoint":"plugin-entry","exportName":"emptyPluginConfigSchema","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"function","recordType":"export","sourceLine":13,"sourcePath":"src/plugins/config-schema.ts"}
{"declaration":"export type AnyAgentTool = AnyAgentTool;","entrypoint":"plugin-entry","exportName":"AnyAgentTool","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":9,"sourcePath":"src/agents/tools/common.ts"}
{"declaration":"export type MediaUnderstandingProviderPlugin = MediaUnderstandingProvider;","entrypoint":"plugin-entry","exportName":"MediaUnderstandingProviderPlugin","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":951,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type MediaUnderstandingProviderPlugin = MediaUnderstandingProvider;","entrypoint":"plugin-entry","exportName":"MediaUnderstandingProviderPlugin","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":964,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type OpenClawConfig = OpenClawConfig;","entrypoint":"plugin-entry","exportName":"OpenClawConfig","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":32,"sourcePath":"src/config/types.openclaw.ts"}
{"declaration":"export type OpenClawPluginApi = OpenClawPluginApi;","entrypoint":"plugin-entry","exportName":"OpenClawPluginApi","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":1336,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type OpenClawPluginCommandDefinition = OpenClawPluginCommandDefinition;","entrypoint":"plugin-entry","exportName":"OpenClawPluginCommandDefinition","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":1068,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type OpenClawPluginConfigSchema = OpenClawPluginConfigSchema;","entrypoint":"plugin-entry","exportName":"OpenClawPluginConfigSchema","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":88,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type OpenClawPluginDefinition = OpenClawPluginDefinition;","entrypoint":"plugin-entry","exportName":"OpenClawPluginDefinition","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":1318,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type OpenClawPluginService = OpenClawPluginService;","entrypoint":"plugin-entry","exportName":"OpenClawPluginService","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":1285,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type OpenClawPluginServiceContext = OpenClawPluginServiceContext;","entrypoint":"plugin-entry","exportName":"OpenClawPluginServiceContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":1277,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type PluginCommandContext = PluginCommandContext;","entrypoint":"plugin-entry","exportName":"PluginCommandContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":966,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type PluginInteractiveTelegramHandlerContext = PluginInteractiveTelegramHandlerContext;","entrypoint":"plugin-entry","exportName":"PluginInteractiveTelegramHandlerContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":1097,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type PluginLogger = PluginLogger;","entrypoint":"plugin-entry","exportName":"PluginLogger","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":59,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderAugmentModelCatalogContext = ProviderAugmentModelCatalogContext;","entrypoint":"plugin-entry","exportName":"ProviderAugmentModelCatalogContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":571,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderAuthContext = ProviderAuthContext;","entrypoint":"plugin-entry","exportName":"ProviderAuthContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":155,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderAuthDoctorHintContext = ProviderAuthDoctorHintContext;","entrypoint":"plugin-entry","exportName":"ProviderAuthDoctorHintContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":446,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderAuthMethod = ProviderAuthMethod;","entrypoint":"plugin-entry","exportName":"ProviderAuthMethod","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":233,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderAuthMethodNonInteractiveContext = ProviderAuthMethodNonInteractiveContext;","entrypoint":"plugin-entry","exportName":"ProviderAuthMethodNonInteractiveContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":217,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderAuthResult = ProviderAuthResult;","entrypoint":"plugin-entry","exportName":"ProviderAuthResult","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":140,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderBuildMissingAuthMessageContext = ProviderBuildMissingAuthMessageContext;","entrypoint":"plugin-entry","exportName":"ProviderBuildMissingAuthMessageContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":499,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderBuiltInModelSuppressionContext = ProviderBuiltInModelSuppressionContext;","entrypoint":"plugin-entry","exportName":"ProviderBuiltInModelSuppressionContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":515,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderBuiltInModelSuppressionResult = ProviderBuiltInModelSuppressionResult;","entrypoint":"plugin-entry","exportName":"ProviderBuiltInModelSuppressionResult","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":524,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderCacheTtlEligibilityContext = ProviderCacheTtlEligibilityContext;","entrypoint":"plugin-entry","exportName":"ProviderCacheTtlEligibilityContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":487,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderCatalogContext = ProviderCatalogContext;","entrypoint":"plugin-entry","exportName":"ProviderCatalogContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":254,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderCatalogResult = ProviderCatalogResult;","entrypoint":"plugin-entry","exportName":"ProviderCatalogResult","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":277,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderDefaultThinkingPolicyContext = ProviderDefaultThinkingPolicyContext;","entrypoint":"plugin-entry","exportName":"ProviderDefaultThinkingPolicyContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":548,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderDiscoveryContext = ProviderCatalogContext;","entrypoint":"plugin-entry","exportName":"ProviderDiscoveryContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":587,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderFetchUsageSnapshotContext = ProviderFetchUsageSnapshotContext;","entrypoint":"plugin-entry","exportName":"ProviderFetchUsageSnapshotContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":427,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderModernModelPolicyContext = ProviderModernModelPolicyContext;","entrypoint":"plugin-entry","exportName":"ProviderModernModelPolicyContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":558,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderNormalizeResolvedModelContext = ProviderNormalizeResolvedModelContext;","entrypoint":"plugin-entry","exportName":"ProviderNormalizeResolvedModelContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":338,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderPreparedRuntimeAuth = ProviderPreparedRuntimeAuth;","entrypoint":"plugin-entry","exportName":"ProviderPreparedRuntimeAuth","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":374,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderPrepareDynamicModelContext = ProviderResolveDynamicModelContext;","entrypoint":"plugin-entry","exportName":"ProviderPrepareDynamicModelContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":329,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderPrepareExtraParamsContext = ProviderPrepareExtraParamsContext;","entrypoint":"plugin-entry","exportName":"ProviderPrepareExtraParamsContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":460,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderPrepareRuntimeAuthContext = ProviderPrepareRuntimeAuthContext;","entrypoint":"plugin-entry","exportName":"ProviderPrepareRuntimeAuthContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":353,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderResolvedUsageAuth = ProviderResolvedUsageAuth;","entrypoint":"plugin-entry","exportName":"ProviderResolvedUsageAuth","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":414,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderResolveDynamicModelContext = ProviderResolveDynamicModelContext;","entrypoint":"plugin-entry","exportName":"ProviderResolveDynamicModelContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":312,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderResolveUsageAuthContext = ProviderResolveUsageAuthContext;","entrypoint":"plugin-entry","exportName":"ProviderResolveUsageAuthContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":395,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderRuntimeModel = ProviderRuntimeModel;","entrypoint":"plugin-entry","exportName":"ProviderRuntimeModel","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":295,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderThinkingPolicyContext = ProviderThinkingPolicyContext;","entrypoint":"plugin-entry","exportName":"ProviderThinkingPolicyContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":536,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderWrapStreamFnContext = ProviderWrapStreamFnContext;","entrypoint":"plugin-entry","exportName":"ProviderWrapStreamFnContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":477,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type SpeechProviderPlugin = SpeechProviderPlugin;","entrypoint":"plugin-entry","exportName":"SpeechProviderPlugin","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":933,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type OpenClawPluginApi = OpenClawPluginApi;","entrypoint":"plugin-entry","exportName":"OpenClawPluginApi","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":1349,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type OpenClawPluginCommandDefinition = OpenClawPluginCommandDefinition;","entrypoint":"plugin-entry","exportName":"OpenClawPluginCommandDefinition","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":1081,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type OpenClawPluginConfigSchema = OpenClawPluginConfigSchema;","entrypoint":"plugin-entry","exportName":"OpenClawPluginConfigSchema","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":94,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type OpenClawPluginDefinition = OpenClawPluginDefinition;","entrypoint":"plugin-entry","exportName":"OpenClawPluginDefinition","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":1331,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type OpenClawPluginService = OpenClawPluginService;","entrypoint":"plugin-entry","exportName":"OpenClawPluginService","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":1298,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type OpenClawPluginServiceContext = OpenClawPluginServiceContext;","entrypoint":"plugin-entry","exportName":"OpenClawPluginServiceContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":1290,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type PluginCommandContext = PluginCommandContext;","entrypoint":"plugin-entry","exportName":"PluginCommandContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":979,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type PluginInteractiveTelegramHandlerContext = PluginInteractiveTelegramHandlerContext;","entrypoint":"plugin-entry","exportName":"PluginInteractiveTelegramHandlerContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":1110,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type PluginLogger = PluginLogger;","entrypoint":"plugin-entry","exportName":"PluginLogger","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":65,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderAugmentModelCatalogContext = ProviderAugmentModelCatalogContext;","entrypoint":"plugin-entry","exportName":"ProviderAugmentModelCatalogContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":577,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderAuthContext = ProviderAuthContext;","entrypoint":"plugin-entry","exportName":"ProviderAuthContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":161,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderAuthDoctorHintContext = ProviderAuthDoctorHintContext;","entrypoint":"plugin-entry","exportName":"ProviderAuthDoctorHintContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":452,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderAuthMethod = ProviderAuthMethod;","entrypoint":"plugin-entry","exportName":"ProviderAuthMethod","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":239,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderAuthMethodNonInteractiveContext = ProviderAuthMethodNonInteractiveContext;","entrypoint":"plugin-entry","exportName":"ProviderAuthMethodNonInteractiveContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":223,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderAuthResult = ProviderAuthResult;","entrypoint":"plugin-entry","exportName":"ProviderAuthResult","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":146,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderBuildMissingAuthMessageContext = ProviderBuildMissingAuthMessageContext;","entrypoint":"plugin-entry","exportName":"ProviderBuildMissingAuthMessageContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":505,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderBuiltInModelSuppressionContext = ProviderBuiltInModelSuppressionContext;","entrypoint":"plugin-entry","exportName":"ProviderBuiltInModelSuppressionContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":521,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderBuiltInModelSuppressionResult = ProviderBuiltInModelSuppressionResult;","entrypoint":"plugin-entry","exportName":"ProviderBuiltInModelSuppressionResult","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":530,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderCacheTtlEligibilityContext = ProviderCacheTtlEligibilityContext;","entrypoint":"plugin-entry","exportName":"ProviderCacheTtlEligibilityContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":493,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderCatalogContext = ProviderCatalogContext;","entrypoint":"plugin-entry","exportName":"ProviderCatalogContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":260,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderCatalogResult = ProviderCatalogResult;","entrypoint":"plugin-entry","exportName":"ProviderCatalogResult","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":283,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderDefaultThinkingPolicyContext = ProviderDefaultThinkingPolicyContext;","entrypoint":"plugin-entry","exportName":"ProviderDefaultThinkingPolicyContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":554,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderDiscoveryContext = ProviderCatalogContext;","entrypoint":"plugin-entry","exportName":"ProviderDiscoveryContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":593,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderFetchUsageSnapshotContext = ProviderFetchUsageSnapshotContext;","entrypoint":"plugin-entry","exportName":"ProviderFetchUsageSnapshotContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":433,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderModernModelPolicyContext = ProviderModernModelPolicyContext;","entrypoint":"plugin-entry","exportName":"ProviderModernModelPolicyContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":564,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderNormalizeResolvedModelContext = ProviderNormalizeResolvedModelContext;","entrypoint":"plugin-entry","exportName":"ProviderNormalizeResolvedModelContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":344,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderPreparedRuntimeAuth = ProviderPreparedRuntimeAuth;","entrypoint":"plugin-entry","exportName":"ProviderPreparedRuntimeAuth","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":380,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderPrepareDynamicModelContext = ProviderResolveDynamicModelContext;","entrypoint":"plugin-entry","exportName":"ProviderPrepareDynamicModelContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":335,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderPrepareExtraParamsContext = ProviderPrepareExtraParamsContext;","entrypoint":"plugin-entry","exportName":"ProviderPrepareExtraParamsContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":466,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderPrepareRuntimeAuthContext = ProviderPrepareRuntimeAuthContext;","entrypoint":"plugin-entry","exportName":"ProviderPrepareRuntimeAuthContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":359,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderResolvedUsageAuth = ProviderResolvedUsageAuth;","entrypoint":"plugin-entry","exportName":"ProviderResolvedUsageAuth","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":420,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderResolveDynamicModelContext = ProviderResolveDynamicModelContext;","entrypoint":"plugin-entry","exportName":"ProviderResolveDynamicModelContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":318,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderResolveUsageAuthContext = ProviderResolveUsageAuthContext;","entrypoint":"plugin-entry","exportName":"ProviderResolveUsageAuthContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":401,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderRuntimeModel = ProviderRuntimeModel;","entrypoint":"plugin-entry","exportName":"ProviderRuntimeModel","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":301,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderThinkingPolicyContext = ProviderThinkingPolicyContext;","entrypoint":"plugin-entry","exportName":"ProviderThinkingPolicyContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":542,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type ProviderWrapStreamFnContext = ProviderWrapStreamFnContext;","entrypoint":"plugin-entry","exportName":"ProviderWrapStreamFnContext","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":483,"sourcePath":"src/plugins/types.ts"}
{"declaration":"export type SpeechProviderPlugin = SpeechProviderPlugin;","entrypoint":"plugin-entry","exportName":"SpeechProviderPlugin","importSpecifier":"openclaw/plugin-sdk/plugin-entry","kind":"type","recordType":"export","sourceLine":939,"sourcePath":"src/plugins/types.ts"}
{"category":"provider","entrypoint":"provider-onboard","importSpecifier":"openclaw/plugin-sdk/provider-onboard","recordType":"module","sourceLine":1,"sourcePath":"src/plugin-sdk/provider-onboard.ts"}
{"declaration":"export function applyAgentDefaultModelPrimary(cfg: OpenClawConfig, primary: string): OpenClawConfig;","entrypoint":"provider-onboard","exportName":"applyAgentDefaultModelPrimary","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"function","recordType":"export","sourceLine":76,"sourcePath":"src/plugins/provider-onboarding-config.ts"}
{"declaration":"export function applyCloudflareAiGatewayConfig(cfg: OpenClawConfig, params?: { accountId?: string | undefined; gatewayId?: string | undefined; } | undefined): OpenClawConfig;","entrypoint":"provider-onboard","exportName":"applyCloudflareAiGatewayConfig","importSpecifier":"openclaw/plugin-sdk/provider-onboard","kind":"function","recordType":"export","sourceLine":85,"sourcePath":"extensions/cloudflare-ai-gateway/onboard.ts"}

View File

@@ -645,11 +645,10 @@ export class DiscordVoiceManager {
cfg: this.params.cfg,
override: this.params.discordConfig.voice?.tts,
});
const directive = parseTtsDirectives(
replyText,
ttsConfig.modelOverrides,
ttsConfig.openai.baseUrl,
);
const directive = parseTtsDirectives(replyText, ttsConfig.modelOverrides, {
cfg: ttsCfg,
providerConfigs: ttsConfig.providerConfigs,
});
const speakText = directive.overrides.ttsText ?? directive.cleanedText.trim();
if (!speakText) {
logVoiceVerbose(

View File

@@ -1,16 +1,309 @@
import type { SpeechProviderPlugin } from "openclaw/plugin-sdk/core";
import type { SpeechVoiceOption } from "openclaw/plugin-sdk/speech";
import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
import type {
SpeechDirectiveTokenParseContext,
SpeechProviderConfig,
SpeechProviderOverrides,
SpeechProviderPlugin,
SpeechVoiceOption,
} from "openclaw/plugin-sdk/speech-core";
import {
normalizeApplyTextNormalization,
normalizeLanguageCode,
normalizeSeed,
requireInRange,
} from "openclaw/plugin-sdk/speech-core";
import { elevenLabsTTS } from "./tts.js";
const DEFAULT_ELEVENLABS_BASE_URL = "https://api.elevenlabs.io";
const DEFAULT_ELEVENLABS_VOICE_ID = "pMsXgVXv3BLzUgSXRplE";
const DEFAULT_ELEVENLABS_MODEL_ID = "eleven_multilingual_v2";
const DEFAULT_ELEVENLABS_VOICE_SETTINGS = {
stability: 0.5,
similarityBoost: 0.75,
style: 0.0,
useSpeakerBoost: true,
speed: 1.0,
};
const ELEVENLABS_TTS_MODELS = [
"eleven_multilingual_v2",
"eleven_turbo_v2_5",
"eleven_monolingual_v1",
] as const;
type ElevenLabsProviderConfig = {
apiKey?: string;
baseUrl: string;
voiceId: string;
modelId: string;
seed?: number;
applyTextNormalization?: "auto" | "on" | "off";
languageCode?: string;
voiceSettings: {
stability: number;
similarityBoost: number;
style: number;
useSpeakerBoost: boolean;
speed: number;
};
};
function trimToUndefined(value: unknown): string | undefined {
return typeof value === "string" && value.trim() ? value.trim() : undefined;
}
function asNumber(value: unknown): number | undefined {
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
}
function asBoolean(value: unknown): boolean | undefined {
return typeof value === "boolean" ? value : undefined;
}
function asObject(value: unknown): Record<string, unknown> | undefined {
return typeof value === "object" && value !== null && !Array.isArray(value)
? (value as Record<string, unknown>)
: undefined;
}
function parseBooleanValue(value: string): boolean | undefined {
const normalized = value.trim().toLowerCase();
if (["true", "1", "yes", "on"].includes(normalized)) {
return true;
}
if (["false", "0", "no", "off"].includes(normalized)) {
return false;
}
return undefined;
}
function parseNumberValue(value: string): number | undefined {
const parsed = Number.parseFloat(value);
return Number.isFinite(parsed) ? parsed : undefined;
}
export function isValidVoiceId(voiceId: string): boolean {
return /^[a-zA-Z0-9]{10,40}$/.test(voiceId);
}
function normalizeElevenLabsBaseUrl(baseUrl: string | undefined): string {
const trimmed = baseUrl?.trim();
return trimmed?.replace(/\/+$/, "") || "https://api.elevenlabs.io";
return trimmed?.replace(/\/+$/, "") || DEFAULT_ELEVENLABS_BASE_URL;
}
function normalizeElevenLabsProviderConfig(
rawConfig: Record<string, unknown>,
): ElevenLabsProviderConfig {
const raw = asObject(rawConfig.elevenlabs);
const rawVoiceSettings = asObject(raw?.voiceSettings);
return {
apiKey: normalizeResolvedSecretInputString({
value: raw?.apiKey,
path: "messages.tts.elevenlabs.apiKey",
}),
baseUrl: normalizeElevenLabsBaseUrl(trimToUndefined(raw?.baseUrl)),
voiceId: trimToUndefined(raw?.voiceId) ?? DEFAULT_ELEVENLABS_VOICE_ID,
modelId: trimToUndefined(raw?.modelId) ?? DEFAULT_ELEVENLABS_MODEL_ID,
seed: asNumber(raw?.seed),
applyTextNormalization: trimToUndefined(raw?.applyTextNormalization) as
| "auto"
| "on"
| "off"
| undefined,
languageCode: trimToUndefined(raw?.languageCode),
voiceSettings: {
stability:
asNumber(rawVoiceSettings?.stability) ?? DEFAULT_ELEVENLABS_VOICE_SETTINGS.stability,
similarityBoost:
asNumber(rawVoiceSettings?.similarityBoost) ??
DEFAULT_ELEVENLABS_VOICE_SETTINGS.similarityBoost,
style: asNumber(rawVoiceSettings?.style) ?? DEFAULT_ELEVENLABS_VOICE_SETTINGS.style,
useSpeakerBoost:
asBoolean(rawVoiceSettings?.useSpeakerBoost) ??
DEFAULT_ELEVENLABS_VOICE_SETTINGS.useSpeakerBoost,
speed: asNumber(rawVoiceSettings?.speed) ?? DEFAULT_ELEVENLABS_VOICE_SETTINGS.speed,
},
};
}
function readElevenLabsProviderConfig(config: SpeechProviderConfig): ElevenLabsProviderConfig {
const defaults = normalizeElevenLabsProviderConfig({});
const voiceSettings = asObject(config.voiceSettings);
return {
apiKey: trimToUndefined(config.apiKey) ?? defaults.apiKey,
baseUrl: normalizeElevenLabsBaseUrl(trimToUndefined(config.baseUrl) ?? defaults.baseUrl),
voiceId: trimToUndefined(config.voiceId) ?? defaults.voiceId,
modelId: trimToUndefined(config.modelId) ?? defaults.modelId,
seed: asNumber(config.seed) ?? defaults.seed,
applyTextNormalization:
(trimToUndefined(config.applyTextNormalization) as "auto" | "on" | "off" | undefined) ??
defaults.applyTextNormalization,
languageCode: trimToUndefined(config.languageCode) ?? defaults.languageCode,
voiceSettings: {
stability: asNumber(voiceSettings?.stability) ?? defaults.voiceSettings.stability,
similarityBoost:
asNumber(voiceSettings?.similarityBoost) ?? defaults.voiceSettings.similarityBoost,
style: asNumber(voiceSettings?.style) ?? defaults.voiceSettings.style,
useSpeakerBoost:
asBoolean(voiceSettings?.useSpeakerBoost) ?? defaults.voiceSettings.useSpeakerBoost,
speed: asNumber(voiceSettings?.speed) ?? defaults.voiceSettings.speed,
},
};
}
function mergeVoiceSettingsOverride(
ctx: SpeechDirectiveTokenParseContext,
next: Record<string, unknown>,
): SpeechProviderOverrides {
return {
...(ctx.currentOverrides ?? {}),
voiceSettings: {
...(asObject(ctx.currentOverrides?.voiceSettings) ?? {}),
...next,
},
};
}
function parseDirectiveToken(ctx: SpeechDirectiveTokenParseContext) {
try {
switch (ctx.key) {
case "voiceid":
case "voice_id":
case "elevenlabs_voice":
case "elevenlabsvoice":
if (!ctx.policy.allowVoice) {
return { handled: true };
}
if (!isValidVoiceId(ctx.value)) {
return { handled: true, warnings: [`invalid ElevenLabs voiceId "${ctx.value}"`] };
}
return {
handled: true,
overrides: { ...(ctx.currentOverrides ?? {}), voiceId: ctx.value },
};
case "model":
case "modelid":
case "model_id":
case "elevenlabs_model":
case "elevenlabsmodel":
if (!ctx.policy.allowModelId) {
return { handled: true };
}
return {
handled: true,
overrides: { ...(ctx.currentOverrides ?? {}), modelId: ctx.value },
};
case "stability": {
if (!ctx.policy.allowVoiceSettings) {
return { handled: true };
}
const value = parseNumberValue(ctx.value);
if (value == null) {
return { handled: true, warnings: ["invalid stability value"] };
}
requireInRange(value, 0, 1, "stability");
return { handled: true, overrides: mergeVoiceSettingsOverride(ctx, { stability: value }) };
}
case "similarity":
case "similarityboost":
case "similarity_boost": {
if (!ctx.policy.allowVoiceSettings) {
return { handled: true };
}
const value = parseNumberValue(ctx.value);
if (value == null) {
return { handled: true, warnings: ["invalid similarityBoost value"] };
}
requireInRange(value, 0, 1, "similarityBoost");
return {
handled: true,
overrides: mergeVoiceSettingsOverride(ctx, { similarityBoost: value }),
};
}
case "style": {
if (!ctx.policy.allowVoiceSettings) {
return { handled: true };
}
const value = parseNumberValue(ctx.value);
if (value == null) {
return { handled: true, warnings: ["invalid style value"] };
}
requireInRange(value, 0, 1, "style");
return { handled: true, overrides: mergeVoiceSettingsOverride(ctx, { style: value }) };
}
case "speed": {
if (!ctx.policy.allowVoiceSettings) {
return { handled: true };
}
const value = parseNumberValue(ctx.value);
if (value == null) {
return { handled: true, warnings: ["invalid speed value"] };
}
requireInRange(value, 0.5, 2, "speed");
return { handled: true, overrides: mergeVoiceSettingsOverride(ctx, { speed: value }) };
}
case "speakerboost":
case "speaker_boost":
case "usespeakerboost":
case "use_speaker_boost": {
if (!ctx.policy.allowVoiceSettings) {
return { handled: true };
}
const value = parseBooleanValue(ctx.value);
if (value == null) {
return { handled: true, warnings: ["invalid useSpeakerBoost value"] };
}
return {
handled: true,
overrides: mergeVoiceSettingsOverride(ctx, { useSpeakerBoost: value }),
};
}
case "normalize":
case "applytextnormalization":
case "apply_text_normalization":
if (!ctx.policy.allowNormalization) {
return { handled: true };
}
return {
handled: true,
overrides: {
...(ctx.currentOverrides ?? {}),
applyTextNormalization: normalizeApplyTextNormalization(ctx.value),
},
};
case "language":
case "languagecode":
case "language_code":
if (!ctx.policy.allowNormalization) {
return { handled: true };
}
return {
handled: true,
overrides: {
...(ctx.currentOverrides ?? {}),
languageCode: normalizeLanguageCode(ctx.value),
},
};
case "seed":
if (!ctx.policy.allowSeed) {
return { handled: true };
}
return {
handled: true,
overrides: {
...(ctx.currentOverrides ?? {}),
seed: normalizeSeed(Number.parseInt(ctx.value, 10)),
},
};
default:
return { handled: false };
}
} catch (error) {
return {
handled: true,
warnings: [error instanceof Error ? error.message : String(error)],
};
}
}
export async function listElevenLabsVoices(params: {
@@ -49,49 +342,164 @@ export function buildElevenLabsSpeechProvider(): SpeechProviderPlugin {
return {
id: "elevenlabs",
label: "ElevenLabs",
autoSelectOrder: 20,
models: ELEVENLABS_TTS_MODELS,
resolveConfig: ({ rawConfig }) => normalizeElevenLabsProviderConfig(rawConfig),
parseDirectiveToken,
resolveTalkConfig: ({ baseTtsConfig, talkProviderConfig }) => {
const base = normalizeElevenLabsProviderConfig(baseTtsConfig);
const talkVoiceSettings = asObject(talkProviderConfig.voiceSettings);
return {
...base,
...(talkProviderConfig.apiKey === undefined
? {}
: {
apiKey: normalizeResolvedSecretInputString({
value: talkProviderConfig.apiKey,
path: "talk.providers.elevenlabs.apiKey",
}),
}),
...(trimToUndefined(talkProviderConfig.baseUrl) == null
? {}
: { baseUrl: normalizeElevenLabsBaseUrl(trimToUndefined(talkProviderConfig.baseUrl)) }),
...(trimToUndefined(talkProviderConfig.voiceId) == null
? {}
: { voiceId: trimToUndefined(talkProviderConfig.voiceId) }),
...(trimToUndefined(talkProviderConfig.modelId) == null
? {}
: { modelId: trimToUndefined(talkProviderConfig.modelId) }),
...(asNumber(talkProviderConfig.seed) == null
? {}
: { seed: asNumber(talkProviderConfig.seed) }),
...(trimToUndefined(talkProviderConfig.applyTextNormalization) == null
? {}
: {
applyTextNormalization: normalizeApplyTextNormalization(
trimToUndefined(talkProviderConfig.applyTextNormalization),
),
}),
...(trimToUndefined(talkProviderConfig.languageCode) == null
? {}
: {
languageCode: normalizeLanguageCode(trimToUndefined(talkProviderConfig.languageCode)),
}),
voiceSettings: {
...base.voiceSettings,
...(asNumber(talkVoiceSettings?.stability) == null
? {}
: { stability: asNumber(talkVoiceSettings?.stability) }),
...(asNumber(talkVoiceSettings?.similarityBoost) == null
? {}
: { similarityBoost: asNumber(talkVoiceSettings?.similarityBoost) }),
...(asNumber(talkVoiceSettings?.style) == null
? {}
: { style: asNumber(talkVoiceSettings?.style) }),
...(asBoolean(talkVoiceSettings?.useSpeakerBoost) == null
? {}
: { useSpeakerBoost: asBoolean(talkVoiceSettings?.useSpeakerBoost) }),
...(asNumber(talkVoiceSettings?.speed) == null
? {}
: { speed: asNumber(talkVoiceSettings?.speed) }),
},
};
},
resolveTalkOverrides: ({ params }) => {
const normalize = trimToUndefined(params.normalize);
const language = trimToUndefined(params.language)?.toLowerCase();
const voiceSettings = {
...(asNumber(params.speed) == null ? {} : { speed: asNumber(params.speed) }),
...(asNumber(params.stability) == null ? {} : { stability: asNumber(params.stability) }),
...(asNumber(params.similarity) == null
? {}
: { similarityBoost: asNumber(params.similarity) }),
...(asNumber(params.style) == null ? {} : { style: asNumber(params.style) }),
...(asBoolean(params.speakerBoost) == null
? {}
: { useSpeakerBoost: asBoolean(params.speakerBoost) }),
};
return {
...(trimToUndefined(params.voiceId) == null
? {}
: { voiceId: trimToUndefined(params.voiceId) }),
...(trimToUndefined(params.modelId) == null
? {}
: { modelId: trimToUndefined(params.modelId) }),
...(trimToUndefined(params.outputFormat) == null
? {}
: { outputFormat: trimToUndefined(params.outputFormat) }),
...(asNumber(params.seed) == null ? {} : { seed: asNumber(params.seed) }),
...(normalize == null
? {}
: { applyTextNormalization: normalizeApplyTextNormalization(normalize) }),
...(language == null ? {} : { languageCode: normalizeLanguageCode(language) }),
...(Object.keys(voiceSettings).length === 0 ? {} : { voiceSettings }),
};
},
listVoices: async (req) => {
const config = req.providerConfig
? readElevenLabsProviderConfig(req.providerConfig)
: undefined;
const apiKey =
req.apiKey ||
req.config?.elevenlabs.apiKey ||
process.env.ELEVENLABS_API_KEY ||
process.env.XI_API_KEY;
req.apiKey || config?.apiKey || process.env.ELEVENLABS_API_KEY || process.env.XI_API_KEY;
if (!apiKey) {
throw new Error("ElevenLabs API key missing");
}
return listElevenLabsVoices({
apiKey,
baseUrl: req.baseUrl ?? req.config?.elevenlabs.baseUrl,
baseUrl: req.baseUrl ?? config?.baseUrl,
});
},
isConfigured: ({ config }) =>
Boolean(config.elevenlabs.apiKey || process.env.ELEVENLABS_API_KEY || process.env.XI_API_KEY),
isConfigured: ({ providerConfig }) =>
Boolean(
readElevenLabsProviderConfig(providerConfig).apiKey ||
process.env.ELEVENLABS_API_KEY ||
process.env.XI_API_KEY,
),
synthesize: async (req) => {
const apiKey =
req.config.elevenlabs.apiKey || process.env.ELEVENLABS_API_KEY || process.env.XI_API_KEY;
const config = readElevenLabsProviderConfig(req.providerConfig);
const overrides = req.providerOverrides ?? {};
const apiKey = config.apiKey || process.env.ELEVENLABS_API_KEY || process.env.XI_API_KEY;
if (!apiKey) {
throw new Error("ElevenLabs API key missing");
}
const outputFormat =
req.overrides?.elevenlabs?.outputFormat ??
trimToUndefined(overrides.outputFormat) ??
(req.target === "voice-note" ? "opus_48000_64" : "mp3_44100_128");
const overrideVoiceSettings = asObject(overrides.voiceSettings);
const audioBuffer = await elevenLabsTTS({
text: req.text,
apiKey,
baseUrl: req.config.elevenlabs.baseUrl,
voiceId: req.overrides?.elevenlabs?.voiceId ?? req.config.elevenlabs.voiceId,
modelId: req.overrides?.elevenlabs?.modelId ?? req.config.elevenlabs.modelId,
baseUrl: config.baseUrl,
voiceId: trimToUndefined(overrides.voiceId) ?? config.voiceId,
modelId: trimToUndefined(overrides.modelId) ?? config.modelId,
outputFormat,
seed: req.overrides?.elevenlabs?.seed ?? req.config.elevenlabs.seed,
seed: asNumber(overrides.seed) ?? config.seed,
applyTextNormalization:
req.overrides?.elevenlabs?.applyTextNormalization ??
req.config.elevenlabs.applyTextNormalization,
languageCode: req.overrides?.elevenlabs?.languageCode ?? req.config.elevenlabs.languageCode,
(trimToUndefined(overrides.applyTextNormalization) as
| "auto"
| "on"
| "off"
| undefined) ?? config.applyTextNormalization,
languageCode: trimToUndefined(overrides.languageCode) ?? config.languageCode,
voiceSettings: {
...req.config.elevenlabs.voiceSettings,
...req.overrides?.elevenlabs?.voiceSettings,
...config.voiceSettings,
...(asNumber(overrideVoiceSettings?.stability) == null
? {}
: { stability: asNumber(overrideVoiceSettings?.stability) }),
...(asNumber(overrideVoiceSettings?.similarityBoost) == null
? {}
: { similarityBoost: asNumber(overrideVoiceSettings?.similarityBoost) }),
...(asNumber(overrideVoiceSettings?.style) == null
? {}
: { style: asNumber(overrideVoiceSettings?.style) }),
...(asBoolean(overrideVoiceSettings?.useSpeakerBoost) == null
? {}
: { useSpeakerBoost: asBoolean(overrideVoiceSettings?.useSpeakerBoost) }),
...(asNumber(overrideVoiceSettings?.speed) == null
? {}
: { speed: asNumber(overrideVoiceSettings?.speed) }),
},
timeoutMs: req.config.timeoutMs,
timeoutMs: req.timeoutMs,
});
return {
audioBuffer,
@@ -101,8 +509,8 @@ export function buildElevenLabsSpeechProvider(): SpeechProviderPlugin {
};
},
synthesizeTelephony: async (req) => {
const apiKey =
req.config.elevenlabs.apiKey || process.env.ELEVENLABS_API_KEY || process.env.XI_API_KEY;
const config = readElevenLabsProviderConfig(req.providerConfig);
const apiKey = config.apiKey || process.env.ELEVENLABS_API_KEY || process.env.XI_API_KEY;
if (!apiKey) {
throw new Error("ElevenLabs API key missing");
}
@@ -111,15 +519,15 @@ export function buildElevenLabsSpeechProvider(): SpeechProviderPlugin {
const audioBuffer = await elevenLabsTTS({
text: req.text,
apiKey,
baseUrl: req.config.elevenlabs.baseUrl,
voiceId: req.config.elevenlabs.voiceId,
modelId: req.config.elevenlabs.modelId,
baseUrl: config.baseUrl,
voiceId: config.voiceId,
modelId: config.modelId,
outputFormat,
seed: req.config.elevenlabs.seed,
applyTextNormalization: req.config.elevenlabs.applyTextNormalization,
languageCode: req.config.elevenlabs.languageCode,
voiceSettings: req.config.elevenlabs.voiceSettings,
timeoutMs: req.config.timeoutMs,
seed: config.seed,
applyTextNormalization: config.applyTextNormalization,
languageCode: config.languageCode,
voiceSettings: config.voiceSettings,
timeoutMs: req.timeoutMs,
});
return { audioBuffer, outputFormat, sampleRate };
},

View File

@@ -5,14 +5,33 @@ import {
TRUSTED_CLIENT_TOKEN,
generateSecMsGecToken,
} from "node-edge-tts/dist/drm.js";
import type { SpeechProviderPlugin } from "openclaw/plugin-sdk/core";
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/llm-task";
import { isVoiceCompatibleAudio } from "openclaw/plugin-sdk/media-runtime";
import type { SpeechVoiceOption } from "openclaw/plugin-sdk/speech";
import type {
SpeechProviderConfig,
SpeechProviderPlugin,
SpeechVoiceOption,
} from "openclaw/plugin-sdk/speech-core";
import { edgeTTS, inferEdgeExtension } from "./tts.js";
const DEFAULT_EDGE_VOICE = "en-US-MichelleNeural";
const DEFAULT_EDGE_LANG = "en-US";
const DEFAULT_EDGE_OUTPUT_FORMAT = "audio-24khz-48kbitrate-mono-mp3";
type MicrosoftProviderConfig = {
enabled: boolean;
voice: string;
lang: string;
outputFormat: string;
outputFormatConfigured: boolean;
pitch?: string;
rate?: string;
volume?: string;
saveSubtitles: boolean;
proxy?: string;
timeoutMs?: number;
};
type MicrosoftVoiceListEntry = {
ShortName?: string;
FriendlyName?: string;
@@ -24,6 +43,64 @@ type MicrosoftVoiceListEntry = {
};
};
function trimToUndefined(value: unknown): string | undefined {
return typeof value === "string" && value.trim() ? value.trim() : undefined;
}
function asBoolean(value: unknown): boolean | undefined {
return typeof value === "boolean" ? value : undefined;
}
function asNumber(value: unknown): number | undefined {
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
}
function asObject(value: unknown): Record<string, unknown> | undefined {
return typeof value === "object" && value !== null && !Array.isArray(value)
? (value as Record<string, unknown>)
: undefined;
}
function normalizeMicrosoftProviderConfig(
rawConfig: Record<string, unknown>,
): MicrosoftProviderConfig {
const rawEdge = asObject(rawConfig.edge);
const rawMicrosoft = asObject(rawConfig.microsoft);
const raw = { ...(rawEdge ?? {}), ...(rawMicrosoft ?? {}) };
const outputFormat = trimToUndefined(raw.outputFormat);
return {
enabled: asBoolean(raw.enabled) ?? true,
voice: trimToUndefined(raw.voice) ?? DEFAULT_EDGE_VOICE,
lang: trimToUndefined(raw.lang) ?? DEFAULT_EDGE_LANG,
outputFormat: outputFormat ?? DEFAULT_EDGE_OUTPUT_FORMAT,
outputFormatConfigured: Boolean(outputFormat),
pitch: trimToUndefined(raw.pitch),
rate: trimToUndefined(raw.rate),
volume: trimToUndefined(raw.volume),
saveSubtitles: asBoolean(raw.saveSubtitles) ?? false,
proxy: trimToUndefined(raw.proxy),
timeoutMs: asNumber(raw.timeoutMs),
};
}
function readMicrosoftProviderConfig(config: SpeechProviderConfig): MicrosoftProviderConfig {
const defaults = normalizeMicrosoftProviderConfig({});
return {
enabled: asBoolean(config.enabled) ?? defaults.enabled,
voice: trimToUndefined(config.voice) ?? defaults.voice,
lang: trimToUndefined(config.lang) ?? defaults.lang,
outputFormat: trimToUndefined(config.outputFormat) ?? defaults.outputFormat,
outputFormatConfigured:
asBoolean(config.outputFormatConfigured) ?? defaults.outputFormatConfigured,
pitch: trimToUndefined(config.pitch) ?? defaults.pitch,
rate: trimToUndefined(config.rate) ?? defaults.rate,
volume: trimToUndefined(config.volume) ?? defaults.volume,
saveSubtitles: asBoolean(config.saveSubtitles) ?? defaults.saveSubtitles,
proxy: trimToUndefined(config.proxy) ?? defaults.proxy,
timeoutMs: asNumber(config.timeoutMs) ?? defaults.timeoutMs,
};
}
function buildMicrosoftVoiceHeaders(): Record<string, string> {
const major = CHROMIUM_FULL_VERSION.split(".")[0] || "0";
return {
@@ -77,13 +154,57 @@ export function buildMicrosoftSpeechProvider(): SpeechProviderPlugin {
id: "microsoft",
label: "Microsoft",
aliases: ["edge"],
autoSelectOrder: 30,
resolveConfig: ({ rawConfig }) => normalizeMicrosoftProviderConfig(rawConfig),
resolveTalkConfig: ({ baseTtsConfig, talkProviderConfig }) => {
const base = normalizeMicrosoftProviderConfig(baseTtsConfig);
return {
...base,
enabled: true,
...(trimToUndefined(talkProviderConfig.voiceId) == null
? {}
: { voice: trimToUndefined(talkProviderConfig.voiceId) }),
...(trimToUndefined(talkProviderConfig.languageCode) == null
? {}
: { lang: trimToUndefined(talkProviderConfig.languageCode) }),
...(trimToUndefined(talkProviderConfig.outputFormat) == null
? {}
: { outputFormat: trimToUndefined(talkProviderConfig.outputFormat) }),
...(trimToUndefined(talkProviderConfig.pitch) == null
? {}
: { pitch: trimToUndefined(talkProviderConfig.pitch) }),
...(trimToUndefined(talkProviderConfig.rate) == null
? {}
: { rate: trimToUndefined(talkProviderConfig.rate) }),
...(trimToUndefined(talkProviderConfig.volume) == null
? {}
: { volume: trimToUndefined(talkProviderConfig.volume) }),
...(trimToUndefined(talkProviderConfig.proxy) == null
? {}
: { proxy: trimToUndefined(talkProviderConfig.proxy) }),
...(asNumber(talkProviderConfig.timeoutMs) == null
? {}
: { timeoutMs: asNumber(talkProviderConfig.timeoutMs) }),
};
},
resolveTalkOverrides: ({ params }) => ({
...(trimToUndefined(params.voiceId) == null
? {}
: { voice: trimToUndefined(params.voiceId) }),
...(trimToUndefined(params.outputFormat) == null
? {}
: { outputFormat: trimToUndefined(params.outputFormat) }),
}),
listVoices: async () => await listMicrosoftVoices(),
isConfigured: ({ config }) => config.edge.enabled,
isConfigured: ({ providerConfig }) => readMicrosoftProviderConfig(providerConfig).enabled,
synthesize: async (req) => {
const config = readMicrosoftProviderConfig(req.providerConfig);
const tempRoot = resolvePreferredOpenClawTmpDir();
mkdirSync(tempRoot, { recursive: true, mode: 0o700 });
const tempDir = mkdtempSync(path.join(tempRoot, "tts-microsoft-"));
let outputFormat = req.overrides?.microsoft?.outputFormat ?? req.config.edge.outputFormat;
const overrideVoice = trimToUndefined(req.providerOverrides?.voice);
let outputFormat =
trimToUndefined(req.providerOverrides?.outputFormat) ?? config.outputFormat;
const fallbackOutputFormat =
outputFormat !== DEFAULT_EDGE_OUTPUT_FORMAT ? DEFAULT_EDGE_OUTPUT_FORMAT : undefined;
@@ -95,11 +216,11 @@ export function buildMicrosoftSpeechProvider(): SpeechProviderPlugin {
text: req.text,
outputPath,
config: {
...req.config.edge,
voice: req.overrides?.microsoft?.voice ?? req.config.edge.voice,
...config,
voice: overrideVoice ?? config.voice,
outputFormat: format,
},
timeoutMs: req.config.timeoutMs,
timeoutMs: req.timeoutMs,
});
const audioBuffer = readFileSync(outputPath);
return {
@@ -112,9 +233,9 @@ export function buildMicrosoftSpeechProvider(): SpeechProviderPlugin {
try {
return await runEdge(outputFormat);
} catch (err) {
} catch (error) {
if (!fallbackOutputFormat || fallbackOutputFormat === outputFormat) {
throw err;
throw error;
}
outputFormat = fallbackOutputFormat;
return await runEdge(outputFormat);

View File

@@ -137,32 +137,14 @@ function createLiveTtsConfig(): ResolvedTtsConfig {
allowNormalization: true,
allowSeed: true,
},
elevenlabs: {
baseUrl: "https://api.elevenlabs.io",
voiceId: "",
modelId: "eleven_multilingual_v2",
voiceSettings: {
stability: 0.5,
similarityBoost: 0.75,
style: 0,
useSpeakerBoost: true,
speed: 1,
providerConfigs: {
openai: {
apiKey: OPENAI_API_KEY,
baseUrl: "https://api.openai.com/v1",
model: "gpt-4o-mini-tts",
voice: "alloy",
},
},
openai: {
apiKey: OPENAI_API_KEY,
baseUrl: "https://api.openai.com/v1",
model: "gpt-4o-mini-tts",
voice: "alloy",
},
edge: {
enabled: false,
voice: "en-US-AriaNeural",
lang: "en-US",
outputFormat: "audio-24khz-48kbitrate-mono-mp3",
outputFormatConfigured: false,
saveSubtitles: false,
},
maxTextLength: 4_000,
timeoutMs: 30_000,
};
@@ -358,8 +340,9 @@ describeLive("openai plugin live", () => {
const audioFile = await speechProvider.synthesize({
text: "OpenClaw integration test OK.",
cfg,
config: ttsConfig,
providerConfig: ttsConfig.providerConfigs.openai ?? {},
target: "audio-file",
timeoutMs: ttsConfig.timeoutMs,
});
expect(audioFile.outputFormat).toBe("mp3");
expect(audioFile.fileExtension).toBe(".mp3");
@@ -368,7 +351,8 @@ describeLive("openai plugin live", () => {
const telephony = await speechProvider.synthesizeTelephony?.({
text: "Telephony check OK.",
cfg,
config: ttsConfig,
providerConfig: ttsConfig.providerConfigs.openai ?? {},
timeoutMs: ttsConfig.timeoutMs,
});
expect(telephony?.outputFormat).toBe("pcm");
expect(telephony?.sampleRate).toBe(24_000);
@@ -386,8 +370,9 @@ describeLive("openai plugin live", () => {
const synthesized = await speechProvider.synthesize({
text: "OpenClaw integration test OK.",
cfg,
config: ttsConfig,
providerConfig: ttsConfig.providerConfigs.openai ?? {},
target: "audio-file",
timeoutMs: ttsConfig.timeoutMs,
});
const transcription = await mediaProvider.transcribeAudio?.({

View File

@@ -1,16 +1,181 @@
import type { SpeechProviderPlugin } from "openclaw/plugin-sdk/core";
import { OPENAI_TTS_MODELS, OPENAI_TTS_VOICES, openaiTTS } from "./tts.js";
import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
import type {
SpeechDirectiveTokenParseContext,
SpeechProviderConfig,
SpeechProviderOverrides,
SpeechProviderPlugin,
} from "openclaw/plugin-sdk/speech-core";
import {
DEFAULT_OPENAI_BASE_URL,
isValidOpenAIModel,
isValidOpenAIVoice,
normalizeOpenAITtsBaseUrl,
OPENAI_TTS_MODELS,
OPENAI_TTS_VOICES,
openaiTTS,
} from "./tts.js";
type OpenAITtsProviderConfig = {
apiKey?: string;
baseUrl: string;
model: string;
voice: string;
speed?: number;
instructions?: string;
};
type OpenAITtsProviderOverrides = {
model?: string;
voice?: string;
speed?: number;
};
function trimToUndefined(value: unknown): string | undefined {
return typeof value === "string" && value.trim() ? value.trim() : undefined;
}
function asNumber(value: unknown): number | undefined {
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
}
function asObject(value: unknown): Record<string, unknown> | undefined {
return typeof value === "object" && value !== null && !Array.isArray(value)
? (value as Record<string, unknown>)
: undefined;
}
function normalizeOpenAIProviderConfig(
rawConfig: Record<string, unknown>,
): OpenAITtsProviderConfig {
const raw = asObject(rawConfig.openai);
return {
apiKey: normalizeResolvedSecretInputString({
value: raw?.apiKey,
path: "messages.tts.openai.apiKey",
}),
baseUrl: normalizeOpenAITtsBaseUrl(
trimToUndefined(raw?.baseUrl) ??
trimToUndefined(process.env.OPENAI_TTS_BASE_URL) ??
DEFAULT_OPENAI_BASE_URL,
),
model: trimToUndefined(raw?.model) ?? "gpt-4o-mini-tts",
voice: trimToUndefined(raw?.voice) ?? "coral",
speed: asNumber(raw?.speed),
instructions: trimToUndefined(raw?.instructions),
};
}
function readOpenAIProviderConfig(config: SpeechProviderConfig): OpenAITtsProviderConfig {
const normalized = normalizeOpenAIProviderConfig({});
return {
apiKey: trimToUndefined(config.apiKey) ?? normalized.apiKey,
baseUrl: trimToUndefined(config.baseUrl) ?? normalized.baseUrl,
model: trimToUndefined(config.model) ?? normalized.model,
voice: trimToUndefined(config.voice) ?? normalized.voice,
speed: asNumber(config.speed) ?? normalized.speed,
instructions: trimToUndefined(config.instructions) ?? normalized.instructions,
};
}
function readOpenAIOverrides(
overrides: SpeechProviderOverrides | undefined,
): OpenAITtsProviderOverrides {
if (!overrides) {
return {};
}
return {
model: trimToUndefined(overrides.model),
voice: trimToUndefined(overrides.voice),
speed: asNumber(overrides.speed),
};
}
function parseDirectiveToken(ctx: SpeechDirectiveTokenParseContext): {
handled: boolean;
overrides?: SpeechProviderOverrides;
warnings?: string[];
} {
const baseUrl = trimToUndefined(ctx.providerConfig?.baseUrl);
switch (ctx.key) {
case "voice":
case "openai_voice":
case "openaivoice":
if (!ctx.policy.allowVoice) {
return { handled: true };
}
if (!isValidOpenAIVoice(ctx.value, baseUrl)) {
return { handled: true, warnings: [`invalid OpenAI voice "${ctx.value}"`] };
}
return { handled: true, overrides: { voice: ctx.value } };
case "model":
case "openai_model":
case "openaimodel":
if (!ctx.policy.allowModelId) {
return { handled: true };
}
if (!isValidOpenAIModel(ctx.value, baseUrl)) {
return { handled: false };
}
return { handled: true, overrides: { model: ctx.value } };
default:
return { handled: false };
}
}
export function buildOpenAISpeechProvider(): SpeechProviderPlugin {
return {
id: "openai",
label: "OpenAI",
autoSelectOrder: 10,
models: OPENAI_TTS_MODELS,
voices: OPENAI_TTS_VOICES,
resolveConfig: ({ rawConfig }) => normalizeOpenAIProviderConfig(rawConfig),
parseDirectiveToken,
resolveTalkConfig: ({ baseTtsConfig, talkProviderConfig }) => {
const base = normalizeOpenAIProviderConfig(baseTtsConfig);
return {
...base,
...(talkProviderConfig.apiKey === undefined
? {}
: {
apiKey: normalizeResolvedSecretInputString({
value: talkProviderConfig.apiKey,
path: "talk.providers.openai.apiKey",
}),
}),
...(trimToUndefined(talkProviderConfig.baseUrl) == null
? {}
: { baseUrl: trimToUndefined(talkProviderConfig.baseUrl) }),
...(trimToUndefined(talkProviderConfig.modelId) == null
? {}
: { model: trimToUndefined(talkProviderConfig.modelId) }),
...(trimToUndefined(talkProviderConfig.voiceId) == null
? {}
: { voice: trimToUndefined(talkProviderConfig.voiceId) }),
...(asNumber(talkProviderConfig.speed) == null
? {}
: { speed: asNumber(talkProviderConfig.speed) }),
...(trimToUndefined(talkProviderConfig.instructions) == null
? {}
: { instructions: trimToUndefined(talkProviderConfig.instructions) }),
};
},
resolveTalkOverrides: ({ params }) => ({
...(trimToUndefined(params.voiceId) == null
? {}
: { voice: trimToUndefined(params.voiceId) }),
...(trimToUndefined(params.modelId) == null
? {}
: { model: trimToUndefined(params.modelId) }),
...(asNumber(params.speed) == null ? {} : { speed: asNumber(params.speed) }),
}),
listVoices: async () => OPENAI_TTS_VOICES.map((voice) => ({ id: voice, name: voice })),
isConfigured: ({ config }) => Boolean(config.openai.apiKey || process.env.OPENAI_API_KEY),
isConfigured: ({ providerConfig }) =>
Boolean(readOpenAIProviderConfig(providerConfig).apiKey || process.env.OPENAI_API_KEY),
synthesize: async (req) => {
const apiKey = req.config.openai.apiKey || process.env.OPENAI_API_KEY;
const config = readOpenAIProviderConfig(req.providerConfig);
const overrides = readOpenAIOverrides(req.providerOverrides);
const apiKey = config.apiKey || process.env.OPENAI_API_KEY;
if (!apiKey) {
throw new Error("OpenAI API key missing");
}
@@ -18,13 +183,13 @@ export function buildOpenAISpeechProvider(): SpeechProviderPlugin {
const audioBuffer = await openaiTTS({
text: req.text,
apiKey,
baseUrl: req.config.openai.baseUrl,
model: req.overrides?.openai?.model ?? req.config.openai.model,
voice: req.overrides?.openai?.voice ?? req.config.openai.voice,
speed: req.overrides?.openai?.speed ?? req.config.openai.speed,
instructions: req.config.openai.instructions,
baseUrl: config.baseUrl,
model: overrides.model ?? config.model,
voice: overrides.voice ?? config.voice,
speed: overrides.speed ?? config.speed,
instructions: config.instructions,
responseFormat,
timeoutMs: req.config.timeoutMs,
timeoutMs: req.timeoutMs,
});
return {
audioBuffer,
@@ -34,7 +199,8 @@ export function buildOpenAISpeechProvider(): SpeechProviderPlugin {
};
},
synthesizeTelephony: async (req) => {
const apiKey = req.config.openai.apiKey || process.env.OPENAI_API_KEY;
const config = readOpenAIProviderConfig(req.providerConfig);
const apiKey = config.apiKey || process.env.OPENAI_API_KEY;
if (!apiKey) {
throw new Error("OpenAI API key missing");
}
@@ -43,13 +209,13 @@ export function buildOpenAISpeechProvider(): SpeechProviderPlugin {
const audioBuffer = await openaiTTS({
text: req.text,
apiKey,
baseUrl: req.config.openai.baseUrl,
model: req.config.openai.model,
voice: req.config.openai.voice,
speed: req.config.openai.speed,
instructions: req.config.openai.instructions,
baseUrl: config.baseUrl,
model: config.model,
voice: config.voice,
speed: config.speed,
instructions: config.instructions,
responseFormat: outputFormat,
timeoutMs: req.config.timeoutMs,
timeoutMs: req.timeoutMs,
});
return { audioBuffer, outputFormat, sampleRate };
},

View File

@@ -1,4 +1,4 @@
const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
export const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
export const OPENAI_TTS_MODELS = ["gpt-4o-mini-tts", "tts-1", "tts-1-hd"] as const;
@@ -21,7 +21,7 @@ export const OPENAI_TTS_VOICES = [
type OpenAiTtsVoice = (typeof OPENAI_TTS_VOICES)[number];
function normalizeOpenAITtsBaseUrl(baseUrl?: string): string {
export function normalizeOpenAITtsBaseUrl(baseUrl?: string): string {
const trimmed = baseUrl?.trim();
if (!trimmed) {
return DEFAULT_OPENAI_BASE_URL;
@@ -36,21 +36,24 @@ function isCustomOpenAIEndpoint(baseUrl?: string): boolean {
return normalizeOpenAITtsBaseUrl(process.env.OPENAI_TTS_BASE_URL) !== DEFAULT_OPENAI_BASE_URL;
}
function isValidOpenAIModel(model: string, baseUrl?: string): boolean {
export function isValidOpenAIModel(model: string, baseUrl?: string): boolean {
if (isCustomOpenAIEndpoint(baseUrl)) {
return true;
}
return OPENAI_TTS_MODELS.includes(model as (typeof OPENAI_TTS_MODELS)[number]);
}
function isValidOpenAIVoice(voice: string, baseUrl?: string): voice is OpenAiTtsVoice {
export function isValidOpenAIVoice(voice: string, baseUrl?: string): voice is OpenAiTtsVoice {
if (isCustomOpenAIEndpoint(baseUrl)) {
return true;
}
return OPENAI_TTS_VOICES.includes(voice as OpenAiTtsVoice);
}
function resolveOpenAITtsInstructions(model: string, instructions?: string): string | undefined {
export function resolveOpenAITtsInstructions(
model: string,
instructions?: string,
): string | undefined {
const next = instructions?.trim();
return next && model.includes("gpt-4o-mini-tts") ? next : undefined;
}

View File

@@ -1,4 +1,3 @@
import { resolveOpenAITtsInstructions } from "../../api.js";
import { convertPcmToMulaw8k } from "../telephony-audio.js";
/**
@@ -72,6 +71,11 @@ function trimToUndefined(value: string | undefined): string | undefined {
return trimmed ? trimmed : undefined;
}
function resolveOpenAITtsInstructions(model: string, instructions?: string): string | undefined {
const next = trimToUndefined(instructions);
return next && model.includes("gpt-4o-mini-tts") ? next : undefined;
}
/**
* OpenAI TTS Provider for generating speech audio.
*/

View File

@@ -8,13 +8,6 @@ const DEFAULT_OUTPUT_PATH = "src/plugins/bundled-plugin-metadata.generated.ts";
const DEFAULT_ENTRIES_OUTPUT_PATH = "src/generated/bundled-plugin-entries.generated.ts";
const MANIFEST_KEY = "openclaw";
const FORMATTER_CWD = path.resolve(import.meta.dirname, "..");
const CANONICAL_PACKAGE_ID_ALIASES = {
"elevenlabs-speech": "elevenlabs",
"microsoft-speech": "microsoft",
"ollama-provider": "ollama",
"sglang-provider": "sglang",
"vllm-provider": "vllm",
};
function rewriteEntryToBuiltPath(entry) {
if (typeof entry !== "string" || entry.trim().length === 0) {
@@ -24,8 +17,12 @@ function rewriteEntryToBuiltPath(entry) {
return normalized.replace(/\.[^.]+$/u, ".js");
}
function deriveIdHint({ filePath, packageName, hasMultipleExtensions }) {
function deriveIdHint({ filePath, manifestId, packageName, hasMultipleExtensions }) {
const base = path.basename(filePath, path.extname(filePath));
const normalizedManifestId = manifestId?.trim();
if (normalizedManifestId) {
return hasMultipleExtensions ? `${normalizedManifestId}/${base}` : normalizedManifestId;
}
const rawPackageName = packageName?.trim();
if (!rawPackageName) {
return base;
@@ -34,11 +31,10 @@ function deriveIdHint({ filePath, packageName, hasMultipleExtensions }) {
const unscoped = rawPackageName.includes("/")
? (rawPackageName.split("/").pop() ?? rawPackageName)
: rawPackageName;
const canonicalPackageId = CANONICAL_PACKAGE_ID_ALIASES[unscoped] ?? unscoped;
const normalizedPackageId =
canonicalPackageId.endsWith("-provider") && canonicalPackageId.length > "-provider".length
? canonicalPackageId.slice(0, -"-provider".length)
: canonicalPackageId;
unscoped.endsWith("-provider") && unscoped.length > "-provider".length
? unscoped.slice(0, -"-provider".length)
: unscoped;
if (!hasMultipleExtensions) {
return normalizedPackageId;
@@ -184,6 +180,7 @@ export function collectBundledPluginMetadata(params = {}) {
dirName: source.dirName,
idHint: deriveIdHint({
filePath: sourceEntry,
manifestId: manifest.id,
packageName: typeof packageJson.name === "string" ? packageJson.name : undefined,
hasMultipleExtensions: extensions.length > 1,
}),
@@ -219,19 +216,34 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = ${JSON.stringify(entries, null,
export function renderBundledPluginEntriesModule(entries) {
const imports = entries
.map((entry) => {
const identifier = toIdentifier(entry.dirName);
const importPath = normalizeGeneratedImportPath(entry.dirName, entry.source.built);
return `import ${identifier} from "${importPath}";`;
return ` import("${importPath}")`;
})
.join("\n");
const identifiers = entries.map((entry) => toIdentifier(entry.dirName)).join(",\n ");
.join(",\n");
const bindings = entries
.map((entry) => {
const identifier = toIdentifier(entry.dirName);
return `${identifier}Module`;
})
.join(",\n ");
const identifiers = entries
.map((entry) => {
const identifier = toIdentifier(entry.dirName);
return `${identifier}Module.default`;
})
.join(",\n ");
return `// Auto-generated by ${GENERATED_BY}. Do not edit directly.
export async function loadGeneratedBundledPluginEntries() {
const [
${bindings}
] = await Promise.all([
${imports}
export const GENERATED_BUNDLED_PLUGIN_ENTRIES = [
${identifiers}
] as const;
]);
return [
${identifiers}
] as const;
}
`;
}

View File

@@ -257,7 +257,7 @@ export function buildBuiltinChatCommands(): ChatCommandDefinition[] {
"• On Enable TTS for responses\n" +
"• Off Disable TTS\n" +
"• Status Show current settings\n" +
"• Provider Set voice provider (edge, elevenlabs, openai)\n" +
"• Provider Show or set the voice provider\n" +
"• Limit Set max characters for TTS\n" +
"• Summary Toggle AI summary for long texts\n" +
"• Audio Generate TTS from custom text\n" +

View File

@@ -1,13 +1,17 @@
import { logVerbose } from "../../globals.js";
import { getSpeechProvider, normalizeSpeechProviderId } from "../../tts/provider-registry.js";
import {
canonicalizeSpeechProviderId,
getSpeechProvider,
listSpeechProviders,
} from "../../tts/provider-registry.js";
import {
getResolvedSpeechProviderConfig,
getLastTtsAttempt,
getTtsMaxLength,
getTtsProvider,
isSummarizationEnabled,
isTtsEnabled,
isTtsProviderConfigured,
resolveTtsApiKey,
resolveTtsConfig,
resolveTtsPrefsPath,
setLastTtsAttempt,
@@ -55,15 +59,13 @@ function ttsUsage(): ReplyPayload {
`• /tts summary [on|off] — View/change auto-summary\n` +
`• /tts audio <text> — Generate audio from text\n\n` +
`**Providers:**\n` +
`• microsoft — Microsoft Edge-backed speech (default fallback)\n` +
`• openai — High quality (requires API key)\n` +
`• elevenlabs — Premium voices (requires API key)\n\n` +
`Use /tts provider to list the registered speech providers and their status.\n\n` +
`**Text Limit (default: 1500, max: 4096):**\n` +
`When text exceeds the limit:\n` +
`• Summary ON: AI summarizes, then generates audio\n` +
`• Summary OFF: Truncates text, then generates audio\n\n` +
`**Examples:**\n` +
`/tts provider microsoft\n` +
`/tts provider <id>\n` +
`/tts limit 2000\n` +
`/tts audio Hello, this is a test!`,
};
@@ -160,30 +162,44 @@ export const handleTtsCommands: CommandHandler = async (params, allowTextCommand
if (action === "provider") {
const currentProvider = getTtsProvider(config, prefsPath);
if (!args.trim()) {
const hasOpenAI = Boolean(resolveTtsApiKey(config, "openai"));
const hasElevenLabs = Boolean(resolveTtsApiKey(config, "elevenlabs"));
const hasMicrosoft = isTtsProviderConfigured(config, "microsoft", params.cfg);
const providers = listSpeechProviders(params.cfg);
return {
shouldContinue: false,
reply: {
text:
`🎙️ TTS provider\n` +
`Primary: ${currentProvider}\n` +
`OpenAI key: ${hasOpenAI ? "✅" : "❌"}\n` +
`ElevenLabs key: ${hasElevenLabs ? "✅" : "❌"}\n` +
`Microsoft enabled: ${hasMicrosoft ? "✅" : "❌"}\n` +
`Usage: /tts provider openai | elevenlabs | microsoft`,
providers
.map(
(provider) =>
`${provider.label}: ${
provider.isConfigured({
cfg: params.cfg,
providerConfig: getResolvedSpeechProviderConfig(
config,
provider.id,
params.cfg,
),
timeoutMs: config.timeoutMs,
})
? "✅"
: "❌"
}`,
)
.join("\n") +
`\nUsage: /tts provider <id>`,
},
};
}
const requested = args.trim().toLowerCase();
if (requested !== "edge" && !getSpeechProvider(requested, params.cfg)) {
const resolvedProvider = getSpeechProvider(requested, params.cfg);
if (!resolvedProvider) {
return { shouldContinue: false, reply: ttsUsage() };
}
const nextProvider = normalizeSpeechProviderId(requested) ?? requested;
setTtsProvider(prefsPath, requested);
const nextProvider = canonicalizeSpeechProviderId(requested, params.cfg) ?? resolvedProvider.id;
setTtsProvider(prefsPath, nextProvider);
return {
shouldContinue: false,
reply: { text: `✅ TTS provider set to ${nextProvider}.` },

View File

@@ -3,7 +3,7 @@ import { redactConfigObject } from "../../config/redact-snapshot.js";
import { buildTalkConfigResponse, resolveActiveTalkProviderConfig } from "../../config/talk.js";
import type { TalkProviderConfig } from "../../config/types.gateway.js";
import type { OpenClawConfig, TtsConfig } from "../../config/types.js";
import { normalizeSpeechProviderId } from "../../tts/provider-registry.js";
import { canonicalizeSpeechProviderId, getSpeechProvider } from "../../tts/provider-registry.js";
import { synthesizeSpeech, type TtsDirectiveOverrides } from "../../tts/tts.js";
import {
ErrorCodes,
@@ -18,7 +18,6 @@ import type { GatewayRequestHandlers } from "./types.js";
const ADMIN_SCOPE = "operator.admin";
const TALK_SECRETS_SCOPE = "operator.talk.secrets";
type ElevenLabsVoiceSettings = NonNullable<NonNullable<TtsConfig["elevenlabs"]>["voiceSettings"]>;
function canReadTalkSecrets(client: { connect?: { scopes?: string[] } } | null): boolean {
const scopes = Array.isArray(client?.connect?.scopes) ? client.connect.scopes : [];
@@ -33,27 +32,6 @@ function trimString(value: unknown): string | undefined {
return trimmed.length > 0 ? trimmed : undefined;
}
function finiteNumber(value: unknown): number | undefined {
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
}
function optionalBoolean(value: unknown): boolean | undefined {
return typeof value === "boolean" ? value : undefined;
}
function plainObject(value: unknown): Record<string, unknown> | undefined {
return typeof value === "object" && value !== null && !Array.isArray(value)
? (value as Record<string, unknown>)
: undefined;
}
function normalizeTextNormalization(value: unknown): "auto" | "on" | "off" | undefined {
const normalized = trimString(value)?.toLowerCase();
return normalized === "auto" || normalized === "on" || normalized === "off"
? normalized
: undefined;
}
function normalizeAliasKey(value: string): string {
return value.trim().toLowerCase();
}
@@ -78,100 +56,39 @@ function resolveTalkVoiceId(
return requested;
}
function readTalkVoiceSettings(
providerConfig: TalkProviderConfig,
): ElevenLabsVoiceSettings | undefined {
const source = plainObject(providerConfig.voiceSettings);
if (!source) {
return undefined;
}
const stability = finiteNumber(source.stability);
const similarityBoost = finiteNumber(source.similarityBoost);
const style = finiteNumber(source.style);
const useSpeakerBoost = optionalBoolean(source.useSpeakerBoost);
const speed = finiteNumber(source.speed);
const voiceSettings = {
...(stability == null ? {} : { stability }),
...(similarityBoost == null ? {} : { similarityBoost }),
...(style == null ? {} : { style }),
...(useSpeakerBoost == null ? {} : { useSpeakerBoost }),
...(speed == null ? {} : { speed }),
};
return Object.keys(voiceSettings).length > 0 ? voiceSettings : undefined;
}
function buildTalkTtsConfig(
config: OpenClawConfig,
):
| { cfg: OpenClawConfig; provider: string; providerConfig: TalkProviderConfig }
| { error: string } {
const resolved = resolveActiveTalkProviderConfig(config.talk);
const provider = normalizeSpeechProviderId(resolved?.provider);
const provider = canonicalizeSpeechProviderId(resolved?.provider, config);
if (!resolved || !provider) {
return { error: "talk.speak unavailable: talk provider not configured" };
}
const speechProvider = getSpeechProvider(provider, config);
if (!speechProvider) {
return {
error: `talk.speak unavailable: speech provider "${provider}" does not support Talk mode`,
};
}
const baseTts = config.messages?.tts ?? {};
const providerConfig = resolved.config;
const resolvedProviderConfig =
speechProvider.resolveTalkConfig?.({
cfg: config,
baseTtsConfig: baseTts as Record<string, unknown>,
talkProviderConfig: providerConfig,
timeoutMs: baseTts.timeoutMs ?? 30_000,
}) ?? providerConfig;
const talkTts: TtsConfig = {
...baseTts,
auto: "always",
provider,
[provider]: resolvedProviderConfig,
};
const baseUrl = trimString(providerConfig.baseUrl);
const voiceId = trimString(providerConfig.voiceId);
const modelId = trimString(providerConfig.modelId);
const languageCode = trimString(providerConfig.languageCode);
if (provider === "elevenlabs") {
const seed = finiteNumber(providerConfig.seed);
const applyTextNormalization = normalizeTextNormalization(
providerConfig.applyTextNormalization,
);
const voiceSettings = readTalkVoiceSettings(providerConfig);
talkTts.elevenlabs = {
...baseTts.elevenlabs,
...(providerConfig.apiKey === undefined ? {} : { apiKey: providerConfig.apiKey }),
...(baseUrl == null ? {} : { baseUrl }),
...(voiceId == null ? {} : { voiceId }),
...(modelId == null ? {} : { modelId }),
...(seed == null ? {} : { seed }),
...(applyTextNormalization == null ? {} : { applyTextNormalization }),
...(languageCode == null ? {} : { languageCode }),
...(voiceSettings == null ? {} : { voiceSettings }),
};
} else if (provider === "openai") {
const speed = finiteNumber(providerConfig.speed);
const instructions = trimString(providerConfig.instructions);
talkTts.openai = {
...baseTts.openai,
...(providerConfig.apiKey === undefined ? {} : { apiKey: providerConfig.apiKey }),
...(baseUrl == null ? {} : { baseUrl }),
...(modelId == null ? {} : { model: modelId }),
...(voiceId == null ? {} : { voice: voiceId }),
...(speed == null ? {} : { speed }),
...(instructions == null ? {} : { instructions }),
};
} else if (provider === "microsoft") {
const outputFormat = trimString(providerConfig.outputFormat);
const pitch = trimString(providerConfig.pitch);
const rate = trimString(providerConfig.rate);
const volume = trimString(providerConfig.volume);
const proxy = trimString(providerConfig.proxy);
const timeoutMs = finiteNumber(providerConfig.timeoutMs);
talkTts.microsoft = {
...baseTts.microsoft,
enabled: true,
...(voiceId == null ? {} : { voice: voiceId }),
...(languageCode == null ? {} : { lang: languageCode }),
...(outputFormat == null ? {} : { outputFormat }),
...(pitch == null ? {} : { pitch }),
...(rate == null ? {} : { rate }),
...(volume == null ? {} : { volume }),
...(proxy == null ? {} : { proxy }),
...(timeoutMs == null ? {} : { timeoutMs }),
};
}
return {
provider,
@@ -189,60 +106,31 @@ function buildTalkTtsConfig(
function buildTalkSpeakOverrides(
provider: string,
providerConfig: TalkProviderConfig,
config: OpenClawConfig,
params: Record<string, unknown>,
): TtsDirectiveOverrides {
const voiceId = resolveTalkVoiceId(providerConfig, trimString(params.voiceId));
const modelId = trimString(params.modelId);
const outputFormat = trimString(params.outputFormat);
const speed = finiteNumber(params.speed);
const seed = finiteNumber(params.seed);
const normalize = normalizeTextNormalization(params.normalize);
const language = trimString(params.language)?.toLowerCase();
const overrides: TtsDirectiveOverrides = { provider };
if (provider === "elevenlabs") {
const voiceSettings = {
...(speed == null ? {} : { speed }),
...(finiteNumber(params.stability) == null
? {}
: { stability: finiteNumber(params.stability) }),
...(finiteNumber(params.similarity) == null
? {}
: { similarityBoost: finiteNumber(params.similarity) }),
...(finiteNumber(params.style) == null ? {} : { style: finiteNumber(params.style) }),
...(optionalBoolean(params.speakerBoost) == null
? {}
: { useSpeakerBoost: optionalBoolean(params.speakerBoost) }),
};
overrides.elevenlabs = {
...(voiceId == null ? {} : { voiceId }),
...(modelId == null ? {} : { modelId }),
...(outputFormat == null ? {} : { outputFormat }),
...(seed == null ? {} : { seed }),
...(normalize == null ? {} : { applyTextNormalization: normalize }),
...(language == null ? {} : { languageCode: language }),
...(Object.keys(voiceSettings).length === 0 ? {} : { voiceSettings }),
};
return overrides;
const speechProvider = getSpeechProvider(provider, config);
if (!speechProvider?.resolveTalkOverrides) {
return { provider };
}
if (provider === "openai") {
overrides.openai = {
...(voiceId == null ? {} : { voice: voiceId }),
...(modelId == null ? {} : { model: modelId }),
...(speed == null ? {} : { speed }),
};
return overrides;
const providerOverrides = speechProvider.resolveTalkOverrides({
talkProviderConfig: providerConfig,
params: {
...params,
...(resolveTalkVoiceId(providerConfig, trimString(params.voiceId)) == null
? {}
: { voiceId: resolveTalkVoiceId(providerConfig, trimString(params.voiceId)) }),
},
});
if (!providerOverrides || Object.keys(providerOverrides).length === 0) {
return { provider };
}
if (provider === "microsoft") {
overrides.microsoft = {
...(voiceId == null ? {} : { voice: voiceId }),
...(outputFormat == null ? {} : { outputFormat }),
};
}
return overrides;
return {
provider,
providerOverrides: {
[provider]: providerOverrides,
},
};
}
function inferMimeType(
@@ -350,7 +238,12 @@ export const talkHandlers: GatewayRequestHandlers = {
return;
}
const overrides = buildTalkSpeakOverrides(setup.provider, setup.providerConfig, params);
const overrides = buildTalkSpeakOverrides(
setup.provider,
setup.providerConfig,
snapshot.config,
params,
);
const result = await synthesizeSpeech({
text,
cfg: setup.cfg,

View File

@@ -1,15 +1,15 @@
import { loadConfig } from "../../config/config.js";
import {
canonicalizeSpeechProviderId,
getSpeechProvider,
listSpeechProviders,
normalizeSpeechProviderId,
} from "../../tts/provider-registry.js";
import {
getResolvedSpeechProviderConfig,
getTtsProvider,
isTtsEnabled,
isTtsProviderConfigured,
resolveTtsAutoMode,
resolveTtsApiKey,
resolveTtsConfig,
resolveTtsPrefsPath,
resolveTtsProviderOrder,
@@ -32,6 +32,15 @@ export const ttsHandlers: GatewayRequestHandlers = {
const fallbackProviders = resolveTtsProviderOrder(provider, cfg)
.slice(1)
.filter((candidate) => isTtsProviderConfigured(config, candidate, cfg));
const providerStates = listSpeechProviders(cfg).map((candidate) => ({
id: candidate.id,
label: candidate.label,
configured: candidate.isConfigured({
cfg,
providerConfig: getResolvedSpeechProviderConfig(config, candidate.id, cfg),
timeoutMs: config.timeoutMs,
}),
}));
respond(true, {
enabled: isTtsEnabled(config, prefsPath),
auto: autoMode,
@@ -39,9 +48,7 @@ export const ttsHandlers: GatewayRequestHandlers = {
fallbackProvider: fallbackProviders[0] ?? null,
fallbackProviders,
prefsPath,
hasOpenAIKey: Boolean(resolveTtsApiKey(config, "openai")),
hasElevenLabsKey: Boolean(resolveTtsApiKey(config, "elevenlabs")),
microsoftEnabled: isTtsProviderConfigured(config, "microsoft", cfg),
providerStates,
});
} catch (err) {
respond(false, undefined, errorShape(ErrorCodes.UNAVAILABLE, formatForLog(err)));
@@ -102,17 +109,18 @@ export const ttsHandlers: GatewayRequestHandlers = {
}
},
"tts.setProvider": async ({ params, respond }) => {
const provider = normalizeSpeechProviderId(
typeof params.provider === "string" ? params.provider.trim() : "",
);
const cfg = loadConfig();
const provider = canonicalizeSpeechProviderId(
typeof params.provider === "string" ? params.provider.trim() : "",
cfg,
);
if (!provider || !getSpeechProvider(provider, cfg)) {
respond(
false,
undefined,
errorShape(
ErrorCodes.INVALID_REQUEST,
"Invalid provider. Use a registered TTS provider id such as openai, elevenlabs, or microsoft.",
"Invalid provider. Use a registered TTS provider id.",
),
);
return;
@@ -135,7 +143,11 @@ export const ttsHandlers: GatewayRequestHandlers = {
providers: listSpeechProviders(cfg).map((provider) => ({
id: provider.id,
name: provider.label,
configured: provider.isConfigured({ cfg, config }),
configured: provider.isConfigured({
cfg,
providerConfig: getResolvedSpeechProviderConfig(config, provider.id, cfg),
timeoutMs: config.timeoutMs,
}),
models: [...(provider.models ?? [])],
voices: [...(provider.voices ?? [])],
})),

View File

@@ -1,157 +1,237 @@
// Auto-generated by scripts/generate-bundled-plugin-metadata.mjs. Do not edit directly.
import acpxPlugin from "../../extensions/acpx/index.js";
import amazonBedrockPlugin from "../../extensions/amazon-bedrock/index.js";
import anthropicPlugin from "../../extensions/anthropic/index.js";
import bluebubblesPlugin from "../../extensions/bluebubbles/index.js";
import bravePlugin from "../../extensions/brave/index.js";
import byteplusPlugin from "../../extensions/byteplus/index.js";
import chutesPlugin from "../../extensions/chutes/index.js";
import cloudflareAiGatewayPlugin from "../../extensions/cloudflare-ai-gateway/index.js";
import copilotProxyPlugin from "../../extensions/copilot-proxy/index.js";
import deepgramPlugin from "../../extensions/deepgram/index.js";
import deepseekPlugin from "../../extensions/deepseek/index.js";
import diagnosticsOtelPlugin from "../../extensions/diagnostics-otel/index.js";
import diffsPlugin from "../../extensions/diffs/index.js";
import discordPlugin from "../../extensions/discord/index.js";
import duckduckgoPlugin from "../../extensions/duckduckgo/index.js";
import elevenlabsPlugin from "../../extensions/elevenlabs/index.js";
import exaPlugin from "../../extensions/exa/index.js";
import falPlugin from "../../extensions/fal/index.js";
import feishuPlugin from "../../extensions/feishu/index.js";
import firecrawlPlugin from "../../extensions/firecrawl/index.js";
import githubCopilotPlugin from "../../extensions/github-copilot/index.js";
import googlePlugin from "../../extensions/google/index.js";
import googlechatPlugin from "../../extensions/googlechat/index.js";
import groqPlugin from "../../extensions/groq/index.js";
import huggingfacePlugin from "../../extensions/huggingface/index.js";
import imessagePlugin from "../../extensions/imessage/index.js";
import ircPlugin from "../../extensions/irc/index.js";
import kilocodePlugin from "../../extensions/kilocode/index.js";
import kimiCodingPlugin from "../../extensions/kimi-coding/index.js";
import linePlugin from "../../extensions/line/index.js";
import llmTaskPlugin from "../../extensions/llm-task/index.js";
import lobsterPlugin from "../../extensions/lobster/index.js";
import matrixPlugin from "../../extensions/matrix/index.js";
import mattermostPlugin from "../../extensions/mattermost/index.js";
import memoryCorePlugin from "../../extensions/memory-core/index.js";
import memoryLancedbPlugin from "../../extensions/memory-lancedb/index.js";
import microsoftFoundryPlugin from "../../extensions/microsoft-foundry/index.js";
import microsoftPlugin from "../../extensions/microsoft/index.js";
import minimaxPlugin from "../../extensions/minimax/index.js";
import mistralPlugin from "../../extensions/mistral/index.js";
import modelstudioPlugin from "../../extensions/modelstudio/index.js";
import moonshotPlugin from "../../extensions/moonshot/index.js";
import msteamsPlugin from "../../extensions/msteams/index.js";
import nextcloudTalkPlugin from "../../extensions/nextcloud-talk/index.js";
import nostrPlugin from "../../extensions/nostr/index.js";
import nvidiaPlugin from "../../extensions/nvidia/index.js";
import ollamaPlugin from "../../extensions/ollama/index.js";
import openProsePlugin from "../../extensions/open-prose/index.js";
import openaiPlugin from "../../extensions/openai/index.js";
import opencodeGoPlugin from "../../extensions/opencode-go/index.js";
import opencodePlugin from "../../extensions/opencode/index.js";
import openrouterPlugin from "../../extensions/openrouter/index.js";
import openshellPlugin from "../../extensions/openshell/index.js";
import perplexityPlugin from "../../extensions/perplexity/index.js";
import qianfanPlugin from "../../extensions/qianfan/index.js";
import sglangPlugin from "../../extensions/sglang/index.js";
import signalPlugin from "../../extensions/signal/index.js";
import slackPlugin from "../../extensions/slack/index.js";
import synologyChatPlugin from "../../extensions/synology-chat/index.js";
import syntheticPlugin from "../../extensions/synthetic/index.js";
import tavilyPlugin from "../../extensions/tavily/index.js";
import telegramPlugin from "../../extensions/telegram/index.js";
import tlonPlugin from "../../extensions/tlon/index.js";
import togetherPlugin from "../../extensions/together/index.js";
import twitchPlugin from "../../extensions/twitch/index.js";
import venicePlugin from "../../extensions/venice/index.js";
import vercelAiGatewayPlugin from "../../extensions/vercel-ai-gateway/index.js";
import vllmPlugin from "../../extensions/vllm/index.js";
import voiceCallPlugin from "../../extensions/voice-call/index.js";
import volcenginePlugin from "../../extensions/volcengine/index.js";
import whatsappPlugin from "../../extensions/whatsapp/index.js";
import xaiPlugin from "../../extensions/xai/index.js";
import xiaomiPlugin from "../../extensions/xiaomi/index.js";
import zaiPlugin from "../../extensions/zai/index.js";
import zaloPlugin from "../../extensions/zalo/index.js";
import zalouserPlugin from "../../extensions/zalouser/index.js";
export const GENERATED_BUNDLED_PLUGIN_ENTRIES = [
acpxPlugin,
amazonBedrockPlugin,
anthropicPlugin,
bluebubblesPlugin,
bravePlugin,
byteplusPlugin,
chutesPlugin,
cloudflareAiGatewayPlugin,
copilotProxyPlugin,
deepgramPlugin,
deepseekPlugin,
diagnosticsOtelPlugin,
diffsPlugin,
discordPlugin,
duckduckgoPlugin,
elevenlabsPlugin,
exaPlugin,
falPlugin,
feishuPlugin,
firecrawlPlugin,
githubCopilotPlugin,
googlePlugin,
googlechatPlugin,
groqPlugin,
huggingfacePlugin,
imessagePlugin,
ircPlugin,
kilocodePlugin,
kimiCodingPlugin,
linePlugin,
llmTaskPlugin,
lobsterPlugin,
matrixPlugin,
mattermostPlugin,
memoryCorePlugin,
memoryLancedbPlugin,
microsoftPlugin,
microsoftFoundryPlugin,
minimaxPlugin,
mistralPlugin,
modelstudioPlugin,
moonshotPlugin,
msteamsPlugin,
nextcloudTalkPlugin,
nostrPlugin,
nvidiaPlugin,
ollamaPlugin,
openProsePlugin,
openaiPlugin,
opencodePlugin,
opencodeGoPlugin,
openrouterPlugin,
openshellPlugin,
perplexityPlugin,
qianfanPlugin,
sglangPlugin,
signalPlugin,
slackPlugin,
synologyChatPlugin,
syntheticPlugin,
tavilyPlugin,
telegramPlugin,
tlonPlugin,
togetherPlugin,
twitchPlugin,
venicePlugin,
vercelAiGatewayPlugin,
vllmPlugin,
voiceCallPlugin,
volcenginePlugin,
whatsappPlugin,
xaiPlugin,
xiaomiPlugin,
zaiPlugin,
zaloPlugin,
zalouserPlugin,
] as const;
export async function loadGeneratedBundledPluginEntries() {
const [
acpxPluginModule,
amazonBedrockPluginModule,
anthropicPluginModule,
bluebubblesPluginModule,
bravePluginModule,
byteplusPluginModule,
chutesPluginModule,
cloudflareAiGatewayPluginModule,
copilotProxyPluginModule,
deepgramPluginModule,
deepseekPluginModule,
diagnosticsOtelPluginModule,
diffsPluginModule,
discordPluginModule,
duckduckgoPluginModule,
elevenlabsPluginModule,
exaPluginModule,
falPluginModule,
feishuPluginModule,
firecrawlPluginModule,
githubCopilotPluginModule,
googlePluginModule,
googlechatPluginModule,
groqPluginModule,
huggingfacePluginModule,
imessagePluginModule,
ircPluginModule,
kilocodePluginModule,
kimiCodingPluginModule,
linePluginModule,
llmTaskPluginModule,
lobsterPluginModule,
matrixPluginModule,
mattermostPluginModule,
memoryCorePluginModule,
memoryLancedbPluginModule,
microsoftPluginModule,
microsoftFoundryPluginModule,
minimaxPluginModule,
mistralPluginModule,
modelstudioPluginModule,
moonshotPluginModule,
msteamsPluginModule,
nextcloudTalkPluginModule,
nostrPluginModule,
nvidiaPluginModule,
ollamaPluginModule,
openProsePluginModule,
openaiPluginModule,
opencodePluginModule,
opencodeGoPluginModule,
openrouterPluginModule,
openshellPluginModule,
perplexityPluginModule,
qianfanPluginModule,
sglangPluginModule,
signalPluginModule,
slackPluginModule,
synologyChatPluginModule,
syntheticPluginModule,
tavilyPluginModule,
telegramPluginModule,
tlonPluginModule,
togetherPluginModule,
twitchPluginModule,
venicePluginModule,
vercelAiGatewayPluginModule,
vllmPluginModule,
voiceCallPluginModule,
volcenginePluginModule,
whatsappPluginModule,
xaiPluginModule,
xiaomiPluginModule,
zaiPluginModule,
zaloPluginModule,
zalouserPluginModule,
] = await Promise.all([
import("../../extensions/acpx/index.js"),
import("../../extensions/amazon-bedrock/index.js"),
import("../../extensions/anthropic/index.js"),
import("../../extensions/bluebubbles/index.js"),
import("../../extensions/brave/index.js"),
import("../../extensions/byteplus/index.js"),
import("../../extensions/chutes/index.js"),
import("../../extensions/cloudflare-ai-gateway/index.js"),
import("../../extensions/copilot-proxy/index.js"),
import("../../extensions/deepgram/index.js"),
import("../../extensions/deepseek/index.js"),
import("../../extensions/diagnostics-otel/index.js"),
import("../../extensions/diffs/index.js"),
import("../../extensions/discord/index.js"),
import("../../extensions/duckduckgo/index.js"),
import("../../extensions/elevenlabs/index.js"),
import("../../extensions/exa/index.js"),
import("../../extensions/fal/index.js"),
import("../../extensions/feishu/index.js"),
import("../../extensions/firecrawl/index.js"),
import("../../extensions/github-copilot/index.js"),
import("../../extensions/google/index.js"),
import("../../extensions/googlechat/index.js"),
import("../../extensions/groq/index.js"),
import("../../extensions/huggingface/index.js"),
import("../../extensions/imessage/index.js"),
import("../../extensions/irc/index.js"),
import("../../extensions/kilocode/index.js"),
import("../../extensions/kimi-coding/index.js"),
import("../../extensions/line/index.js"),
import("../../extensions/llm-task/index.js"),
import("../../extensions/lobster/index.js"),
import("../../extensions/matrix/index.js"),
import("../../extensions/mattermost/index.js"),
import("../../extensions/memory-core/index.js"),
import("../../extensions/memory-lancedb/index.js"),
import("../../extensions/microsoft/index.js"),
import("../../extensions/microsoft-foundry/index.js"),
import("../../extensions/minimax/index.js"),
import("../../extensions/mistral/index.js"),
import("../../extensions/modelstudio/index.js"),
import("../../extensions/moonshot/index.js"),
import("../../extensions/msteams/index.js"),
import("../../extensions/nextcloud-talk/index.js"),
import("../../extensions/nostr/index.js"),
import("../../extensions/nvidia/index.js"),
import("../../extensions/ollama/index.js"),
import("../../extensions/open-prose/index.js"),
import("../../extensions/openai/index.js"),
import("../../extensions/opencode/index.js"),
import("../../extensions/opencode-go/index.js"),
import("../../extensions/openrouter/index.js"),
import("../../extensions/openshell/index.js"),
import("../../extensions/perplexity/index.js"),
import("../../extensions/qianfan/index.js"),
import("../../extensions/sglang/index.js"),
import("../../extensions/signal/index.js"),
import("../../extensions/slack/index.js"),
import("../../extensions/synology-chat/index.js"),
import("../../extensions/synthetic/index.js"),
import("../../extensions/tavily/index.js"),
import("../../extensions/telegram/index.js"),
import("../../extensions/tlon/index.js"),
import("../../extensions/together/index.js"),
import("../../extensions/twitch/index.js"),
import("../../extensions/venice/index.js"),
import("../../extensions/vercel-ai-gateway/index.js"),
import("../../extensions/vllm/index.js"),
import("../../extensions/voice-call/index.js"),
import("../../extensions/volcengine/index.js"),
import("../../extensions/whatsapp/index.js"),
import("../../extensions/xai/index.js"),
import("../../extensions/xiaomi/index.js"),
import("../../extensions/zai/index.js"),
import("../../extensions/zalo/index.js"),
import("../../extensions/zalouser/index.js"),
]);
return [
acpxPluginModule.default,
amazonBedrockPluginModule.default,
anthropicPluginModule.default,
bluebubblesPluginModule.default,
bravePluginModule.default,
byteplusPluginModule.default,
chutesPluginModule.default,
cloudflareAiGatewayPluginModule.default,
copilotProxyPluginModule.default,
deepgramPluginModule.default,
deepseekPluginModule.default,
diagnosticsOtelPluginModule.default,
diffsPluginModule.default,
discordPluginModule.default,
duckduckgoPluginModule.default,
elevenlabsPluginModule.default,
exaPluginModule.default,
falPluginModule.default,
feishuPluginModule.default,
firecrawlPluginModule.default,
githubCopilotPluginModule.default,
googlePluginModule.default,
googlechatPluginModule.default,
groqPluginModule.default,
huggingfacePluginModule.default,
imessagePluginModule.default,
ircPluginModule.default,
kilocodePluginModule.default,
kimiCodingPluginModule.default,
linePluginModule.default,
llmTaskPluginModule.default,
lobsterPluginModule.default,
matrixPluginModule.default,
mattermostPluginModule.default,
memoryCorePluginModule.default,
memoryLancedbPluginModule.default,
microsoftPluginModule.default,
microsoftFoundryPluginModule.default,
minimaxPluginModule.default,
mistralPluginModule.default,
modelstudioPluginModule.default,
moonshotPluginModule.default,
msteamsPluginModule.default,
nextcloudTalkPluginModule.default,
nostrPluginModule.default,
nvidiaPluginModule.default,
ollamaPluginModule.default,
openProsePluginModule.default,
openaiPluginModule.default,
opencodePluginModule.default,
opencodeGoPluginModule.default,
openrouterPluginModule.default,
openshellPluginModule.default,
perplexityPluginModule.default,
qianfanPluginModule.default,
sglangPluginModule.default,
signalPluginModule.default,
slackPluginModule.default,
synologyChatPluginModule.default,
syntheticPluginModule.default,
tavilyPluginModule.default,
telegramPluginModule.default,
tlonPluginModule.default,
togetherPluginModule.default,
twitchPluginModule.default,
venicePluginModule.default,
vercelAiGatewayPluginModule.default,
vllmPluginModule.default,
voiceCallPluginModule.default,
volcenginePluginModule.default,
whatsappPluginModule.default,
xaiPluginModule.default,
xiaomiPluginModule.default,
zaiPluginModule.default,
zaloPluginModule.default,
zalouserPluginModule.default,
] as const;
}

View File

@@ -1,12 +1,28 @@
// Shared speech-provider implementation helpers for bundled and third-party plugins.
export type { SpeechProviderPlugin } from "../plugins/types.js";
export type { SpeechVoiceOption } from "../tts/provider-types.js";
export type {
SpeechDirectiveTokenParseContext,
SpeechDirectiveTokenParseResult,
SpeechListVoicesRequest,
SpeechModelOverridePolicy,
SpeechProviderConfig,
SpeechProviderConfiguredContext,
SpeechProviderResolveConfigContext,
SpeechProviderResolveTalkConfigContext,
SpeechProviderResolveTalkOverridesContext,
SpeechProviderOverrides,
SpeechSynthesisRequest,
SpeechTelephonySynthesisRequest,
SpeechVoiceOption,
TtsDirectiveOverrides,
TtsDirectiveParseResult,
} from "../tts/provider-types.js";
export {
normalizeApplyTextNormalization,
normalizeLanguageCode,
normalizeSeed,
parseTtsDirectives,
requireInRange,
} from "../tts/tts-core.js";
export { parseTtsDirectives } from "../tts/directives.js";

View File

@@ -1,4 +1,9 @@
// Public speech helpers for bundled or third-party plugins.
export { parseTtsDirectives } from "../tts/tts-core.js";
export type { SpeechVoiceOption } from "../tts/provider-types.js";
export { parseTtsDirectives } from "../tts/directives.js";
export type {
SpeechModelOverridePolicy,
SpeechVoiceOption,
TtsDirectiveOverrides,
TtsDirectiveParseResult,
} from "../tts/provider-types.js";

View File

@@ -8,7 +8,6 @@ export {
TtsModeSchema,
TtsProviderSchema,
} from "../config/zod-schema.core.js";
export { resolveOpenAITtsInstructions } from "../tts/tts-core.js";
export type { GatewayRequestHandlerOptions } from "../gateway/server-methods/types.js";
export {
isRequestBodyLimitError,

View File

@@ -1,4 +1,4 @@
import { GENERATED_BUNDLED_PLUGIN_ENTRIES } from "../generated/bundled-plugin-entries.generated.js";
import { loadGeneratedBundledPluginEntries } from "../generated/bundled-plugin-entries.generated.js";
import type { OpenClawPluginDefinition } from "./types.js";
type BundledRegistrablePlugin = OpenClawPluginDefinition & {
@@ -7,4 +7,4 @@ type BundledRegistrablePlugin = OpenClawPluginDefinition & {
};
export const BUNDLED_PLUGIN_ENTRIES =
GENERATED_BUNDLED_PLUGIN_ENTRIES as unknown as readonly BundledRegistrablePlugin[];
(await loadGeneratedBundledPluginEntries()) as unknown as readonly BundledRegistrablePlugin[];

View File

@@ -249,7 +249,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [
},
{
dirName: "brave",
idHint: "brave-plugin",
idHint: "brave",
source: {
source: "./index.ts",
built: "index.js",
@@ -820,7 +820,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [
},
{
dirName: "duckduckgo",
idHint: "duckduckgo-plugin",
idHint: "duckduckgo",
source: {
source: "./index.ts",
built: "index.js",
@@ -889,7 +889,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [
},
{
dirName: "exa",
idHint: "exa-plugin",
idHint: "exa",
source: {
source: "./index.ts",
built: "index.js",
@@ -1021,7 +1021,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [
},
{
dirName: "firecrawl",
idHint: "firecrawl-plugin",
idHint: "firecrawl",
source: {
source: "./index.ts",
built: "index.js",
@@ -1109,7 +1109,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [
},
{
dirName: "google",
idHint: "google-plugin",
idHint: "google",
source: {
source: "./index.ts",
built: "index.js",
@@ -2556,7 +2556,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [
},
{
dirName: "openshell",
idHint: "openshell-sandbox",
idHint: "openshell",
source: {
source: "./index.ts",
built: "index.js",
@@ -2670,7 +2670,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [
},
{
dirName: "perplexity",
idHint: "perplexity-plugin",
idHint: "perplexity",
source: {
source: "./index.ts",
built: "index.js",
@@ -2966,7 +2966,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [
},
{
dirName: "tavily",
idHint: "tavily-plugin",
idHint: "tavily",
source: {
source: "./index.ts",
built: "index.js",
@@ -4007,7 +4007,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [
},
{
dirName: "xai",
idHint: "xai-plugin",
idHint: "xai",
source: {
source: "./index.ts",
built: "index.js",

View File

@@ -32,7 +32,13 @@ import type { MediaUnderstandingProvider } from "../media-understanding/types.js
import type { RuntimeEnv } from "../runtime.js";
import type { RuntimeWebSearchMetadata } from "../secrets/runtime-web-tools.types.js";
import type {
SpeechDirectiveTokenParseContext,
SpeechDirectiveTokenParseResult,
SpeechProviderConfiguredContext,
SpeechProviderConfig,
SpeechProviderResolveConfigContext,
SpeechProviderResolveTalkConfigContext,
SpeechProviderResolveTalkOverridesContext,
SpeechListVoicesRequest,
SpeechProviderId,
SpeechSynthesisRequest,
@@ -939,8 +945,15 @@ export type SpeechProviderPlugin = {
id: SpeechProviderId;
label: string;
aliases?: string[];
autoSelectOrder?: number;
models?: readonly string[];
voices?: readonly string[];
resolveConfig?: (ctx: SpeechProviderResolveConfigContext) => SpeechProviderConfig;
parseDirectiveToken?: (ctx: SpeechDirectiveTokenParseContext) => SpeechDirectiveTokenParseResult;
resolveTalkConfig?: (ctx: SpeechProviderResolveTalkConfigContext) => SpeechProviderConfig;
resolveTalkOverrides?: (
ctx: SpeechProviderResolveTalkOverridesContext,
) => SpeechProviderConfig | undefined;
isConfigured: (ctx: SpeechProviderConfiguredContext) => boolean;
synthesize: (req: SpeechSynthesisRequest) => Promise<SpeechSynthesisResult>;
synthesizeTelephony?: (

160
src/tts/directives.ts Normal file
View File

@@ -0,0 +1,160 @@
import type { OpenClawConfig } from "../config/config.js";
import type { SpeechProviderPlugin } from "../plugins/types.js";
import { listSpeechProviders } from "./provider-registry.js";
import type {
SpeechModelOverridePolicy,
SpeechProviderConfig,
SpeechProviderOverrides,
TtsDirectiveOverrides,
TtsDirectiveParseResult,
} from "./provider-types.js";
type ParseTtsDirectiveOptions = {
cfg?: OpenClawConfig;
providers?: readonly SpeechProviderPlugin[];
providerConfigs?: Record<string, SpeechProviderConfig>;
openaiBaseUrl?: string;
};
function buildProviderOrder(left: SpeechProviderPlugin, right: SpeechProviderPlugin): number {
const leftOrder = left.autoSelectOrder ?? Number.MAX_SAFE_INTEGER;
const rightOrder = right.autoSelectOrder ?? Number.MAX_SAFE_INTEGER;
if (leftOrder !== rightOrder) {
return leftOrder - rightOrder;
}
return left.id.localeCompare(right.id);
}
function resolveDirectiveProviders(options?: ParseTtsDirectiveOptions): SpeechProviderPlugin[] {
if (options?.providers) {
return [...options.providers].toSorted(buildProviderOrder);
}
return listSpeechProviders(options?.cfg).toSorted(buildProviderOrder);
}
function resolveDirectiveProviderConfig(
provider: SpeechProviderPlugin,
options?: ParseTtsDirectiveOptions,
): SpeechProviderConfig | undefined {
const explicit = options?.providerConfigs?.[provider.id];
if (explicit) {
return explicit;
}
if (provider.id === "openai" && options?.openaiBaseUrl) {
return { baseUrl: options.openaiBaseUrl };
}
return undefined;
}
function mergeProviderOverrides(
target: TtsDirectiveOverrides,
providerId: string,
next: SpeechProviderOverrides,
): void {
target.providerOverrides = {
...target.providerOverrides,
[providerId]: {
...target.providerOverrides?.[providerId],
...next,
},
};
}
function resolveLegacyOptions(
optionsOrOpenaiBaseUrl?: ParseTtsDirectiveOptions | string,
): ParseTtsDirectiveOptions | undefined {
if (typeof optionsOrOpenaiBaseUrl === "string") {
return { openaiBaseUrl: optionsOrOpenaiBaseUrl };
}
return optionsOrOpenaiBaseUrl;
}
export function parseTtsDirectives(
text: string,
policy: SpeechModelOverridePolicy,
optionsOrOpenaiBaseUrl?: ParseTtsDirectiveOptions | string,
): TtsDirectiveParseResult {
if (!policy.enabled) {
return { cleanedText: text, overrides: {}, warnings: [], hasDirective: false };
}
const options = resolveLegacyOptions(optionsOrOpenaiBaseUrl);
const providers = resolveDirectiveProviders(options);
const overrides: TtsDirectiveOverrides = {};
const warnings: string[] = [];
let cleanedText = text;
let hasDirective = false;
const blockRegex = /\[\[tts:text\]\]([\s\S]*?)\[\[\/tts:text\]\]/gi;
cleanedText = cleanedText.replace(blockRegex, (_match, inner: string) => {
hasDirective = true;
if (policy.allowText && overrides.ttsText == null) {
overrides.ttsText = inner.trim();
}
return "";
});
const directiveRegex = /\[\[tts:([^\]]+)\]\]/gi;
cleanedText = cleanedText.replace(directiveRegex, (_match, body: string) => {
hasDirective = true;
const tokens = body.split(/\s+/).filter(Boolean);
for (const token of tokens) {
const eqIndex = token.indexOf("=");
if (eqIndex === -1) {
continue;
}
const rawKey = token.slice(0, eqIndex).trim();
const rawValue = token.slice(eqIndex + 1).trim();
if (!rawKey || !rawValue) {
continue;
}
const key = rawKey.toLowerCase();
if (key === "provider") {
if (policy.allowProvider) {
const providerId = rawValue.trim().toLowerCase();
if (providerId) {
overrides.provider = providerId;
} else {
warnings.push("invalid provider id");
}
}
continue;
}
let handled = false;
for (const provider of providers) {
const parsed = provider.parseDirectiveToken?.({
key,
value: rawValue,
policy,
providerConfig: resolveDirectiveProviderConfig(provider, options),
currentOverrides: overrides.providerOverrides?.[provider.id],
});
if (!parsed?.handled) {
continue;
}
handled = true;
if (parsed.overrides) {
mergeProviderOverrides(overrides, provider.id, parsed.overrides);
}
if (parsed.warnings?.length) {
warnings.push(...parsed.warnings);
}
break;
}
if (!handled) {
continue;
}
}
return "";
});
return {
cleanedText,
ttsText: overrides.ttsText,
hasDirective,
overrides,
warnings,
};
}

View File

@@ -13,6 +13,7 @@ vi.mock("../plugins/loader.js", () => ({
let getSpeechProvider: typeof import("./provider-registry.js").getSpeechProvider;
let listSpeechProviders: typeof import("./provider-registry.js").listSpeechProviders;
let canonicalizeSpeechProviderId: typeof import("./provider-registry.js").canonicalizeSpeechProviderId;
let normalizeSpeechProviderId: typeof import("./provider-registry.js").normalizeSpeechProviderId;
function createSpeechProvider(id: string, aliases?: string[]): SpeechProviderPlugin {
@@ -36,8 +37,12 @@ describe("speech provider registry", () => {
resetPluginRuntimeStateForTest();
loadOpenClawPluginsMock.mockReset();
loadOpenClawPluginsMock.mockReturnValue(createEmptyPluginRegistry());
({ getSpeechProvider, listSpeechProviders, normalizeSpeechProviderId } =
await import("./provider-registry.js"));
({
getSpeechProvider,
listSpeechProviders,
canonicalizeSpeechProviderId,
normalizeSpeechProviderId,
} = await import("./provider-registry.js"));
});
afterEach(() => {
@@ -86,7 +91,19 @@ describe("speech provider registry", () => {
expect(getSpeechProvider("openai")).toBeUndefined();
});
it("normalizes the legacy edge alias to microsoft", () => {
expect(normalizeSpeechProviderId("edge")).toBe("microsoft");
it("canonicalizes the legacy edge alias to microsoft", () => {
setActivePluginRegistry({
...createEmptyPluginRegistry(),
speechProviders: [
{
pluginId: "test-microsoft",
source: "test",
provider: createSpeechProvider("microsoft", ["edge"]),
},
],
});
expect(normalizeSpeechProviderId("edge")).toBe("edge");
expect(canonicalizeSpeechProviderId("edge")).toBe("microsoft");
});
});

View File

@@ -12,11 +12,7 @@ function trimToUndefined(value: string | undefined): string | undefined {
export function normalizeSpeechProviderId(
providerId: string | undefined,
): SpeechProviderId | undefined {
const normalized = trimToUndefined(providerId);
if (!normalized) {
return undefined;
}
return normalized === "edge" ? "microsoft" : normalized;
return trimToUndefined(providerId);
}
function resolveSpeechProviderPluginEntries(cfg?: OpenClawConfig): SpeechProviderPlugin[] {
@@ -70,3 +66,14 @@ export function getSpeechProvider(
}
return buildProviderMaps(cfg).aliases.get(normalized);
}
export function canonicalizeSpeechProviderId(
providerId: string | undefined,
cfg?: OpenClawConfig,
): SpeechProviderId | undefined {
const normalized = normalizeSpeechProviderId(providerId);
if (!normalized) {
return undefined;
}
return getSpeechProvider(normalized, cfg)?.id ?? normalized;
}

View File

@@ -1,21 +1,52 @@
import type { OpenClawConfig } from "../config/config.js";
import type { ResolvedTtsConfig, TtsDirectiveOverrides } from "./tts.js";
import type { TalkProviderConfig } from "../config/types.gateway.js";
export type SpeechProviderId = string;
export type SpeechSynthesisTarget = "audio-file" | "voice-note";
export type SpeechProviderConfig = Record<string, unknown>;
export type SpeechProviderOverrides = Record<string, unknown>;
export type SpeechModelOverridePolicy = {
enabled: boolean;
allowText: boolean;
allowProvider: boolean;
allowVoice: boolean;
allowModelId: boolean;
allowVoiceSettings: boolean;
allowNormalization: boolean;
allowSeed: boolean;
};
export type TtsDirectiveOverrides = {
ttsText?: string;
provider?: SpeechProviderId;
providerOverrides?: Record<string, SpeechProviderOverrides>;
};
export type TtsDirectiveParseResult = {
cleanedText: string;
ttsText?: string;
hasDirective: boolean;
overrides: TtsDirectiveOverrides;
warnings: string[];
};
export type SpeechProviderConfiguredContext = {
cfg?: OpenClawConfig;
config: ResolvedTtsConfig;
providerConfig: SpeechProviderConfig;
timeoutMs: number;
};
export type SpeechSynthesisRequest = {
text: string;
cfg: OpenClawConfig;
config: ResolvedTtsConfig;
providerConfig: SpeechProviderConfig;
target: SpeechSynthesisTarget;
overrides?: TtsDirectiveOverrides;
providerOverrides?: SpeechProviderOverrides;
timeoutMs: number;
};
export type SpeechSynthesisResult = {
@@ -28,7 +59,8 @@ export type SpeechSynthesisResult = {
export type SpeechTelephonySynthesisRequest = {
text: string;
cfg: OpenClawConfig;
config: ResolvedTtsConfig;
providerConfig: SpeechProviderConfig;
timeoutMs: number;
};
export type SpeechTelephonySynthesisResult = {
@@ -49,7 +81,39 @@ export type SpeechVoiceOption = {
export type SpeechListVoicesRequest = {
cfg?: OpenClawConfig;
config?: ResolvedTtsConfig;
providerConfig?: SpeechProviderConfig;
apiKey?: string;
baseUrl?: string;
};
export type SpeechProviderResolveConfigContext = {
cfg: OpenClawConfig;
rawConfig: Record<string, unknown>;
timeoutMs: number;
};
export type SpeechDirectiveTokenParseContext = {
key: string;
value: string;
policy: SpeechModelOverridePolicy;
providerConfig?: SpeechProviderConfig;
currentOverrides?: SpeechProviderOverrides;
};
export type SpeechDirectiveTokenParseResult = {
handled: boolean;
overrides?: SpeechProviderOverrides;
warnings?: string[];
};
export type SpeechProviderResolveTalkConfigContext = {
cfg: OpenClawConfig;
baseTtsConfig: Record<string, unknown>;
talkProviderConfig: TalkProviderConfig;
timeoutMs: number;
};
export type SpeechProviderResolveTalkOverridesContext = {
talkProviderConfig: TalkProviderConfig;
params: Record<string, unknown>;
};

View File

@@ -10,33 +10,10 @@ import {
import { resolveModelAsync } from "../agents/pi-embedded-runner/model.js";
import { prepareModelForSimpleCompletion } from "../agents/simple-completion-transport.js";
import type { OpenClawConfig } from "../config/config.js";
import type {
ResolvedTtsConfig,
ResolvedTtsModelOverrides,
TtsDirectiveOverrides,
TtsDirectiveParseResult,
} from "./tts.js";
import type { ResolvedTtsConfig } from "./tts.js";
export const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
const TEMP_FILE_CLEANUP_DELAY_MS = 5 * 60 * 1000; // 5 minutes
export function isValidVoiceId(voiceId: string): boolean {
return /^[a-zA-Z0-9]{10,40}$/.test(voiceId);
}
function normalizeOpenAITtsBaseUrl(baseUrl?: string): string {
const trimmed = baseUrl?.trim();
if (!trimmed) {
return DEFAULT_OPENAI_BASE_URL;
}
return trimmed.replace(/\/+$/, "");
}
function trimToUndefined(value?: string): string | undefined {
const trimmed = value?.trim();
return trimmed ? trimmed : undefined;
}
export function requireInRange(value: number, min: number, max: number, label: string): void {
if (!Number.isFinite(value) || value < min || value > max) {
throw new Error(`${label} must be between ${min} and ${max}`);
@@ -78,317 +55,6 @@ export function normalizeSeed(seed?: number): number | undefined {
return next;
}
function parseBooleanValue(value: string): boolean | undefined {
const normalized = value.trim().toLowerCase();
if (["true", "1", "yes", "on"].includes(normalized)) {
return true;
}
if (["false", "0", "no", "off"].includes(normalized)) {
return false;
}
return undefined;
}
function parseNumberValue(value: string): number | undefined {
const parsed = Number.parseFloat(value);
return Number.isFinite(parsed) ? parsed : undefined;
}
export function parseTtsDirectives(
text: string,
policy: ResolvedTtsModelOverrides,
openaiBaseUrl?: string,
): TtsDirectiveParseResult {
if (!policy.enabled) {
return { cleanedText: text, overrides: {}, warnings: [], hasDirective: false };
}
const overrides: TtsDirectiveOverrides = {};
const warnings: string[] = [];
let cleanedText = text;
let hasDirective = false;
const blockRegex = /\[\[tts:text\]\]([\s\S]*?)\[\[\/tts:text\]\]/gi;
cleanedText = cleanedText.replace(blockRegex, (_match, inner: string) => {
hasDirective = true;
if (policy.allowText && overrides.ttsText == null) {
overrides.ttsText = inner.trim();
}
return "";
});
const directiveRegex = /\[\[tts:([^\]]+)\]\]/gi;
cleanedText = cleanedText.replace(directiveRegex, (_match, body: string) => {
hasDirective = true;
const tokens = body.split(/\s+/).filter(Boolean);
for (const token of tokens) {
const eqIndex = token.indexOf("=");
if (eqIndex === -1) {
continue;
}
const rawKey = token.slice(0, eqIndex).trim();
const rawValue = token.slice(eqIndex + 1).trim();
if (!rawKey || !rawValue) {
continue;
}
const key = rawKey.toLowerCase();
try {
switch (key) {
case "provider":
if (!policy.allowProvider) {
break;
}
{
const providerId = rawValue.trim().toLowerCase();
if (providerId) {
overrides.provider = providerId;
} else {
warnings.push("invalid provider id");
}
}
break;
case "voice":
case "openai_voice":
case "openaivoice":
if (!policy.allowVoice) {
break;
}
if (isValidOpenAIVoice(rawValue, openaiBaseUrl)) {
overrides.openai = { ...overrides.openai, voice: rawValue };
} else {
warnings.push(`invalid OpenAI voice "${rawValue}"`);
}
break;
case "voiceid":
case "voice_id":
case "elevenlabs_voice":
case "elevenlabsvoice":
if (!policy.allowVoice) {
break;
}
if (isValidVoiceId(rawValue)) {
overrides.elevenlabs = { ...overrides.elevenlabs, voiceId: rawValue };
} else {
warnings.push(`invalid ElevenLabs voiceId "${rawValue}"`);
}
break;
case "model":
case "modelid":
case "model_id":
case "elevenlabs_model":
case "elevenlabsmodel":
case "openai_model":
case "openaimodel":
if (!policy.allowModelId) {
break;
}
if (isValidOpenAIModel(rawValue, openaiBaseUrl)) {
overrides.openai = { ...overrides.openai, model: rawValue };
} else {
overrides.elevenlabs = { ...overrides.elevenlabs, modelId: rawValue };
}
break;
case "stability":
if (!policy.allowVoiceSettings) {
break;
}
{
const value = parseNumberValue(rawValue);
if (value == null) {
warnings.push("invalid stability value");
break;
}
requireInRange(value, 0, 1, "stability");
overrides.elevenlabs = {
...overrides.elevenlabs,
voiceSettings: { ...overrides.elevenlabs?.voiceSettings, stability: value },
};
}
break;
case "similarity":
case "similarityboost":
case "similarity_boost":
if (!policy.allowVoiceSettings) {
break;
}
{
const value = parseNumberValue(rawValue);
if (value == null) {
warnings.push("invalid similarityBoost value");
break;
}
requireInRange(value, 0, 1, "similarityBoost");
overrides.elevenlabs = {
...overrides.elevenlabs,
voiceSettings: { ...overrides.elevenlabs?.voiceSettings, similarityBoost: value },
};
}
break;
case "style":
if (!policy.allowVoiceSettings) {
break;
}
{
const value = parseNumberValue(rawValue);
if (value == null) {
warnings.push("invalid style value");
break;
}
requireInRange(value, 0, 1, "style");
overrides.elevenlabs = {
...overrides.elevenlabs,
voiceSettings: { ...overrides.elevenlabs?.voiceSettings, style: value },
};
}
break;
case "speed":
if (!policy.allowVoiceSettings) {
break;
}
{
const value = parseNumberValue(rawValue);
if (value == null) {
warnings.push("invalid speed value");
break;
}
requireInRange(value, 0.5, 2, "speed");
overrides.elevenlabs = {
...overrides.elevenlabs,
voiceSettings: { ...overrides.elevenlabs?.voiceSettings, speed: value },
};
}
break;
case "speakerboost":
case "speaker_boost":
case "usespeakerboost":
case "use_speaker_boost":
if (!policy.allowVoiceSettings) {
break;
}
{
const value = parseBooleanValue(rawValue);
if (value == null) {
warnings.push("invalid useSpeakerBoost value");
break;
}
overrides.elevenlabs = {
...overrides.elevenlabs,
voiceSettings: { ...overrides.elevenlabs?.voiceSettings, useSpeakerBoost: value },
};
}
break;
case "normalize":
case "applytextnormalization":
case "apply_text_normalization":
if (!policy.allowNormalization) {
break;
}
overrides.elevenlabs = {
...overrides.elevenlabs,
applyTextNormalization: normalizeApplyTextNormalization(rawValue),
};
break;
case "language":
case "languagecode":
case "language_code":
if (!policy.allowNormalization) {
break;
}
overrides.elevenlabs = {
...overrides.elevenlabs,
languageCode: normalizeLanguageCode(rawValue),
};
break;
case "seed":
if (!policy.allowSeed) {
break;
}
overrides.elevenlabs = {
...overrides.elevenlabs,
seed: normalizeSeed(Number.parseInt(rawValue, 10)),
};
break;
default:
break;
}
} catch (err) {
warnings.push((err as Error).message);
}
}
return "";
});
return {
cleanedText,
ttsText: overrides.ttsText,
hasDirective,
overrides,
warnings,
};
}
export const OPENAI_TTS_MODELS = ["gpt-4o-mini-tts", "tts-1", "tts-1-hd"] as const;
/**
* Custom OpenAI-compatible TTS endpoint.
* When set, model/voice validation is relaxed to allow non-OpenAI models.
* Example: OPENAI_TTS_BASE_URL=http://localhost:8880/v1
*
* Note: Read at runtime (not module load) to support config.env loading.
*/
function getOpenAITtsBaseUrl(): string {
return normalizeOpenAITtsBaseUrl(process.env.OPENAI_TTS_BASE_URL);
}
function isCustomOpenAIEndpoint(baseUrl?: string): boolean {
if (baseUrl != null) {
return normalizeOpenAITtsBaseUrl(baseUrl) !== DEFAULT_OPENAI_BASE_URL;
}
return getOpenAITtsBaseUrl() !== DEFAULT_OPENAI_BASE_URL;
}
export const OPENAI_TTS_VOICES = [
"alloy",
"ash",
"ballad",
"cedar",
"coral",
"echo",
"fable",
"juniper",
"marin",
"onyx",
"nova",
"sage",
"shimmer",
"verse",
] as const;
type OpenAiTtsVoice = (typeof OPENAI_TTS_VOICES)[number];
export function isValidOpenAIModel(model: string, baseUrl?: string): boolean {
// Allow any model when using custom endpoint (e.g., Kokoro, LocalAI)
if (isCustomOpenAIEndpoint(baseUrl)) {
return true;
}
return OPENAI_TTS_MODELS.includes(model as (typeof OPENAI_TTS_MODELS)[number]);
}
export function resolveOpenAITtsInstructions(
model: string,
instructions?: string,
): string | undefined {
const next = trimToUndefined(instructions);
return next && model.includes("gpt-4o-mini-tts") ? next : undefined;
}
export function isValidOpenAIVoice(voice: string, baseUrl?: string): voice is OpenAiTtsVoice {
// Allow any voice when using custom endpoint (e.g., Kokoro Chinese voices)
if (isCustomOpenAIEndpoint(baseUrl)) {
return true;
}
return OPENAI_TTS_VOICES.includes(voice as OpenAiTtsVoice);
}
type SummarizeResult = {
summary: string;
latencyMs: number;

View File

@@ -1,8 +1,18 @@
import type { AssistantMessage } from "@mariozechner/pi-ai";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { buildElevenLabsSpeechProvider } from "../../extensions/elevenlabs/speech-provider.ts";
import {
buildElevenLabsSpeechProvider,
isValidVoiceId,
} from "../../extensions/elevenlabs/speech-provider.ts";
import { buildMicrosoftSpeechProvider } from "../../extensions/microsoft/speech-provider.ts";
import { buildOpenAISpeechProvider } from "../../extensions/openai/speech-provider.ts";
import {
isValidOpenAIModel,
isValidOpenAIVoice,
OPENAI_TTS_MODELS,
OPENAI_TTS_VOICES,
resolveOpenAITtsInstructions,
} from "../../extensions/openai/tts.ts";
import type { OpenClawConfig } from "../config/config.js";
import { createEmptyPluginRegistry } from "../plugins/registry-empty.js";
import { setActivePluginRegistry } from "../plugins/runtime.js";
@@ -73,17 +83,11 @@ vi.mock("../agents/custom-api-registry.js", () => ({
const { _test, resolveTtsConfig, maybeApplyTtsToPayload, getTtsProvider } = tts;
const {
isValidVoiceId,
isValidOpenAIVoice,
isValidOpenAIModel,
OPENAI_TTS_MODELS,
OPENAI_TTS_VOICES,
parseTtsDirectives,
resolveOpenAITtsInstructions,
resolveModelOverridePolicy,
summarizeText,
resolveOutputFormat,
resolveEdgeOutputFormat,
getResolvedSpeechProviderConfig,
} = _test;
const mockAssistantMessage = (content: AssistantMessage["content"]): AssistantMessage => ({
@@ -317,7 +321,10 @@ describe("tts", () => {
] as const;
for (const testCase of cases) {
const config = resolveTtsConfig(testCase.cfg);
expect(resolveEdgeOutputFormat(config), testCase.name).toBe(testCase.expected);
const providerConfig = getResolvedSpeechProviderConfig(config, "microsoft") as {
outputFormat?: string;
};
expect(providerConfig.outputFormat, testCase.name).toBe(testCase.expected);
}
});
});
@@ -329,13 +336,19 @@ describe("tts", () => {
"Hello [[tts:provider=elevenlabs voiceId=pMsXgVXv3BLzUgSXRplE stability=0.4 speed=1.1]] world\n\n" +
"[[tts:text]](laughs) Read the song once more.[[/tts:text]]";
const result = parseTtsDirectives(input, policy);
const elevenlabsOverrides = result.overrides.providerOverrides?.elevenlabs as
| {
voiceId?: string;
voiceSettings?: { stability?: number; speed?: number };
}
| undefined;
expect(result.cleanedText).not.toContain("[[tts:");
expect(result.ttsText).toBe("(laughs) Read the song once more.");
expect(result.overrides.provider).toBe("elevenlabs");
expect(result.overrides.elevenlabs?.voiceId).toBe("pMsXgVXv3BLzUgSXRplE");
expect(result.overrides.elevenlabs?.voiceSettings?.stability).toBe(0.4);
expect(result.overrides.elevenlabs?.voiceSettings?.speed).toBe(1.1);
expect(elevenlabsOverrides?.voiceId).toBe("pMsXgVXv3BLzUgSXRplE");
expect(elevenlabsOverrides?.voiceSettings?.stability).toBe(0.4);
expect(elevenlabsOverrides?.voiceSettings?.speed).toBe(1.1);
});
it("accepts edge as a legacy microsoft provider override", () => {
@@ -350,9 +363,12 @@ describe("tts", () => {
const policy = resolveModelOverridePolicy({ enabled: true });
const input = "Hello [[tts:provider=edge voice=alloy]] world";
const result = parseTtsDirectives(input, policy);
const openaiOverrides = result.overrides.providerOverrides?.openai as
| { voice?: string }
| undefined;
expect(result.overrides.provider).toBeUndefined();
expect(result.overrides.openai?.voice).toBe("alloy");
expect(openaiOverrides?.voice).toBe("alloy");
});
it("keeps text intact when overrides are disabled", () => {
@@ -370,9 +386,12 @@ describe("tts", () => {
const customBaseUrl = "http://localhost:8880/v1";
const result = parseTtsDirectives(input, policy, customBaseUrl);
const openaiOverrides = result.overrides.providerOverrides?.openai as
| { voice?: string; model?: string }
| undefined;
expect(result.overrides.openai?.voice).toBe("kokoro-chinese");
expect(result.overrides.openai?.model).toBe("kokoro-v1");
expect(openaiOverrides?.voice).toBe("kokoro-chinese");
expect(openaiOverrides?.model).toBe("kokoro-v1");
expect(result.warnings).toHaveLength(0);
});
@@ -382,8 +401,11 @@ describe("tts", () => {
const defaultBaseUrl = "https://api.openai.com/v1";
const result = parseTtsDirectives(input, policy, defaultBaseUrl);
const openaiOverrides = result.overrides.providerOverrides?.openai as
| { voice?: string }
| undefined;
expect(result.overrides.openai?.voice).toBeUndefined();
expect(openaiOverrides?.voice).toBeUndefined();
expect(result.warnings).toContain('invalid OpenAI voice "kokoro-chinese"');
});
});
@@ -704,7 +726,10 @@ describe("tts", () => {
] as const) {
withEnv(testCase.env, () => {
const config = resolveTtsConfig(testCase.cfg);
expect(config.openai.baseUrl, testCase.name).toBe(testCase.expected);
const openaiConfig = getResolvedSpeechProviderConfig(config, "openai") as {
baseUrl?: string;
};
expect(openaiConfig.baseUrl, testCase.name).toBe(testCase.expected);
});
}
});

View File

@@ -14,7 +14,6 @@ import type { ReplyPayload } from "../auto-reply/types.js";
import { normalizeChannelId } from "../channels/plugins/index.js";
import type { ChannelId } from "../channels/plugins/types.js";
import type { OpenClawConfig } from "../config/config.js";
import { normalizeResolvedSecretInputString } from "../config/types.secrets.js";
import type {
TtsConfig,
TtsAutoMode,
@@ -24,52 +23,30 @@ import type {
} from "../config/types.tts.js";
import { logVerbose } from "../globals.js";
import { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js";
import {
OPENAI_DEFAULT_TTS_MODEL as DEFAULT_OPENAI_MODEL,
OPENAI_DEFAULT_TTS_VOICE as DEFAULT_OPENAI_VOICE,
} from "../plugins/provider-model-defaults.js";
import { stripMarkdown } from "../shared/text/strip-markdown.js";
import { CONFIG_DIR, resolveUserPath } from "../utils.js";
import { parseTtsDirectives } from "./directives.js";
import {
canonicalizeSpeechProviderId,
getSpeechProvider,
listSpeechProviders,
normalizeSpeechProviderId,
} from "./provider-registry.js";
import type { SpeechVoiceOption } from "./provider-types.js";
import {
DEFAULT_OPENAI_BASE_URL,
isValidOpenAIModel,
isValidOpenAIVoice,
isValidVoiceId,
OPENAI_TTS_MODELS,
OPENAI_TTS_VOICES,
resolveOpenAITtsInstructions,
parseTtsDirectives,
scheduleCleanup,
summarizeText,
} from "./tts-core.js";
export { OPENAI_TTS_MODELS, OPENAI_TTS_VOICES } from "./tts-core.js";
import type {
SpeechModelOverridePolicy,
SpeechProviderConfig,
SpeechVoiceOption,
TtsDirectiveOverrides,
TtsDirectiveParseResult,
} from "./provider-types.js";
import { scheduleCleanup, summarizeText } from "./tts-core.js";
export type { TtsDirectiveOverrides, TtsDirectiveParseResult } from "./provider-types.js";
const DEFAULT_TIMEOUT_MS = 30_000;
const DEFAULT_TTS_MAX_LENGTH = 1500;
const DEFAULT_TTS_SUMMARIZE = true;
const DEFAULT_MAX_TEXT_LENGTH = 4096;
const DEFAULT_ELEVENLABS_BASE_URL = "https://api.elevenlabs.io";
const DEFAULT_ELEVENLABS_VOICE_ID = "pMsXgVXv3BLzUgSXRplE";
const DEFAULT_ELEVENLABS_MODEL_ID = "eleven_multilingual_v2";
const DEFAULT_EDGE_VOICE = "en-US-MichelleNeural";
const DEFAULT_EDGE_LANG = "en-US";
const DEFAULT_EDGE_OUTPUT_FORMAT = "audio-24khz-48kbitrate-mono-mp3";
const DEFAULT_ELEVENLABS_VOICE_SETTINGS = {
stability: 0.5,
similarityBoost: 0.75,
style: 0.0,
useSpeakerBoost: true,
speed: 1.0,
};
const OPUS_OUTPUT = {
openai: "opus" as const,
// ElevenLabs output formats use codec_sample_rate_bitrate naming.
@@ -95,43 +72,7 @@ export type ResolvedTtsConfig = {
providerSource: "config" | "default";
summaryModel?: string;
modelOverrides: ResolvedTtsModelOverrides;
elevenlabs: {
apiKey?: string;
baseUrl: string;
voiceId: string;
modelId: string;
seed?: number;
applyTextNormalization?: "auto" | "on" | "off";
languageCode?: string;
voiceSettings: {
stability: number;
similarityBoost: number;
style: number;
useSpeakerBoost: boolean;
speed: number;
};
};
openai: {
apiKey?: string;
baseUrl: string;
model: string;
voice: string;
speed?: number;
instructions?: string;
};
edge: {
enabled: boolean;
voice: string;
lang: string;
outputFormat: string;
outputFormatConfigured: boolean;
pitch?: string;
rate?: string;
volume?: string;
saveSubtitles: boolean;
proxy?: string;
timeoutMs?: number;
};
providerConfigs: Record<string, SpeechProviderConfig>;
prefsPath?: string;
maxTextLength: number;
timeoutMs: number;
@@ -147,47 +88,7 @@ type TtsUserPrefs = {
};
};
export type ResolvedTtsModelOverrides = {
enabled: boolean;
allowText: boolean;
allowProvider: boolean;
allowVoice: boolean;
allowModelId: boolean;
allowVoiceSettings: boolean;
allowNormalization: boolean;
allowSeed: boolean;
};
export type TtsDirectiveOverrides = {
ttsText?: string;
provider?: TtsProvider;
openai?: {
voice?: string;
model?: string;
speed?: number;
};
elevenlabs?: {
voiceId?: string;
modelId?: string;
outputFormat?: string;
seed?: number;
applyTextNormalization?: "auto" | "on" | "off";
languageCode?: string;
voiceSettings?: Partial<ResolvedTtsConfig["elevenlabs"]["voiceSettings"]>;
};
microsoft?: {
voice?: string;
outputFormat?: string;
};
};
export type TtsDirectiveParseResult = {
cleanedText: string;
ttsText?: string;
hasDirective: boolean;
overrides: TtsDirectiveOverrides;
warnings: string[];
};
export type ResolvedTtsModelOverrides = SpeechModelOverridePolicy;
export type TtsResult = {
success: boolean;
@@ -273,75 +174,66 @@ function resolveModelOverridePolicy(
};
}
function sortSpeechProvidersForAutoSelection(cfg?: OpenClawConfig) {
return listSpeechProviders(cfg).toSorted((left, right) => {
const leftOrder = left.autoSelectOrder ?? Number.MAX_SAFE_INTEGER;
const rightOrder = right.autoSelectOrder ?? Number.MAX_SAFE_INTEGER;
if (leftOrder !== rightOrder) {
return leftOrder - rightOrder;
}
return left.id.localeCompare(right.id);
});
}
function asProviderConfig(value: unknown): SpeechProviderConfig {
return typeof value === "object" && value !== null && !Array.isArray(value)
? (value as SpeechProviderConfig)
: {};
}
function resolveSpeechProviderConfigs(
raw: TtsConfig,
cfg: OpenClawConfig,
timeoutMs: number,
): Record<string, SpeechProviderConfig> {
const providerConfigs: Record<string, SpeechProviderConfig> = {};
for (const provider of listSpeechProviders(cfg)) {
providerConfigs[provider.id] =
provider.resolveConfig?.({
cfg,
rawConfig: raw as Record<string, unknown>,
timeoutMs,
}) ?? asProviderConfig((raw as Record<string, unknown>)[provider.id]);
}
return providerConfigs;
}
export function getResolvedSpeechProviderConfig(
config: ResolvedTtsConfig,
providerId: string,
cfg?: OpenClawConfig,
): SpeechProviderConfig {
const canonical =
canonicalizeSpeechProviderId(providerId, cfg) ?? providerId.trim().toLowerCase();
return config.providerConfigs[canonical] ?? {};
}
export function resolveTtsConfig(cfg: OpenClawConfig): ResolvedTtsConfig {
const raw: TtsConfig = cfg.messages?.tts ?? {};
const providerSource = raw.provider ? "config" : "default";
const rawMicrosoft = { ...raw.edge, ...raw.microsoft };
const edgeOutputFormat = rawMicrosoft.outputFormat?.trim();
const timeoutMs = raw.timeoutMs ?? DEFAULT_TIMEOUT_MS;
const auto = normalizeTtsAutoMode(raw.auto) ?? (raw.enabled ? "always" : "off");
return {
auto,
mode: raw.mode ?? "final",
provider: normalizeSpeechProviderId(raw.provider) ?? "microsoft",
provider: canonicalizeSpeechProviderId(raw.provider, cfg) ?? "microsoft",
providerSource,
summaryModel: raw.summaryModel?.trim() || undefined,
modelOverrides: resolveModelOverridePolicy(raw.modelOverrides),
elevenlabs: {
apiKey: normalizeResolvedSecretInputString({
value: raw.elevenlabs?.apiKey,
path: "messages.tts.elevenlabs.apiKey",
}),
baseUrl: raw.elevenlabs?.baseUrl?.trim() || DEFAULT_ELEVENLABS_BASE_URL,
voiceId: raw.elevenlabs?.voiceId ?? DEFAULT_ELEVENLABS_VOICE_ID,
modelId: raw.elevenlabs?.modelId ?? DEFAULT_ELEVENLABS_MODEL_ID,
seed: raw.elevenlabs?.seed,
applyTextNormalization: raw.elevenlabs?.applyTextNormalization,
languageCode: raw.elevenlabs?.languageCode,
voiceSettings: {
stability:
raw.elevenlabs?.voiceSettings?.stability ?? DEFAULT_ELEVENLABS_VOICE_SETTINGS.stability,
similarityBoost:
raw.elevenlabs?.voiceSettings?.similarityBoost ??
DEFAULT_ELEVENLABS_VOICE_SETTINGS.similarityBoost,
style: raw.elevenlabs?.voiceSettings?.style ?? DEFAULT_ELEVENLABS_VOICE_SETTINGS.style,
useSpeakerBoost:
raw.elevenlabs?.voiceSettings?.useSpeakerBoost ??
DEFAULT_ELEVENLABS_VOICE_SETTINGS.useSpeakerBoost,
speed: raw.elevenlabs?.voiceSettings?.speed ?? DEFAULT_ELEVENLABS_VOICE_SETTINGS.speed,
},
},
openai: {
apiKey: normalizeResolvedSecretInputString({
value: raw.openai?.apiKey,
path: "messages.tts.openai.apiKey",
}),
// Config > env var > default; strip trailing slashes for consistency.
baseUrl: (
raw.openai?.baseUrl?.trim() ||
process.env.OPENAI_TTS_BASE_URL?.trim() ||
DEFAULT_OPENAI_BASE_URL
).replace(/\/+$/, ""),
model: raw.openai?.model ?? DEFAULT_OPENAI_MODEL,
voice: raw.openai?.voice ?? DEFAULT_OPENAI_VOICE,
speed: raw.openai?.speed,
instructions: raw.openai?.instructions?.trim() || undefined,
},
edge: {
enabled: rawMicrosoft.enabled ?? true,
voice: rawMicrosoft.voice?.trim() || DEFAULT_EDGE_VOICE,
lang: rawMicrosoft.lang?.trim() || DEFAULT_EDGE_LANG,
outputFormat: edgeOutputFormat || DEFAULT_EDGE_OUTPUT_FORMAT,
outputFormatConfigured: Boolean(edgeOutputFormat),
pitch: rawMicrosoft.pitch?.trim() || undefined,
rate: rawMicrosoft.rate?.trim() || undefined,
volume: rawMicrosoft.volume?.trim() || undefined,
saveSubtitles: rawMicrosoft.saveSubtitles ?? false,
proxy: rawMicrosoft.proxy?.trim() || undefined,
timeoutMs: rawMicrosoft.timeoutMs,
},
providerConfigs: resolveSpeechProviderConfigs(raw, cfg, timeoutMs),
prefsPath: raw.prefsPath,
maxTextLength: raw.maxTextLength ?? DEFAULT_MAX_TEXT_LENGTH,
timeoutMs: raw.timeoutMs ?? DEFAULT_TIMEOUT_MS,
timeoutMs,
};
}
@@ -464,26 +356,30 @@ export function setTtsEnabled(prefsPath: string, enabled: boolean): void {
export function getTtsProvider(config: ResolvedTtsConfig, prefsPath: string): TtsProvider {
const prefs = readPrefs(prefsPath);
const prefsProvider = normalizeSpeechProviderId(prefs.tts?.provider);
const prefsProvider = canonicalizeSpeechProviderId(prefs.tts?.provider);
if (prefsProvider) {
return prefsProvider;
}
if (config.providerSource === "config") {
return normalizeSpeechProviderId(config.provider) ?? config.provider;
return canonicalizeSpeechProviderId(config.provider) ?? config.provider;
}
if (resolveTtsApiKey(config, "openai")) {
return "openai";
}
if (resolveTtsApiKey(config, "elevenlabs")) {
return "elevenlabs";
for (const provider of sortSpeechProvidersForAutoSelection()) {
if (
provider.isConfigured({
providerConfig: config.providerConfigs[provider.id] ?? {},
timeoutMs: config.timeoutMs,
})
) {
return provider.id;
}
}
return "microsoft";
}
export function setTtsProvider(prefsPath: string, provider: TtsProvider): void {
updatePrefs(prefsPath, (prefs) => {
prefs.tts = { ...prefs.tts, provider: normalizeSpeechProviderId(provider) ?? provider };
prefs.tts = { ...prefs.tts, provider: canonicalizeSpeechProviderId(provider) ?? provider };
});
}
@@ -531,36 +427,11 @@ function resolveChannelId(channel: string | undefined): ChannelId | null {
return channel ? normalizeChannelId(channel) : null;
}
function resolveEdgeOutputFormat(config: ResolvedTtsConfig): string {
return config.edge.outputFormat;
}
export function resolveTtsApiKey(
config: ResolvedTtsConfig,
provider: TtsProvider,
): string | undefined {
const normalizedProvider = normalizeSpeechProviderId(provider);
if (normalizedProvider === "elevenlabs") {
return config.elevenlabs.apiKey || process.env.ELEVENLABS_API_KEY || process.env.XI_API_KEY;
}
if (normalizedProvider === "openai") {
return config.openai.apiKey || process.env.OPENAI_API_KEY;
}
return undefined;
}
export const TTS_PROVIDERS = ["openai", "elevenlabs", "microsoft"] as const;
export function resolveTtsProviderOrder(primary: TtsProvider, cfg?: OpenClawConfig): TtsProvider[] {
const normalizedPrimary = normalizeSpeechProviderId(primary) ?? primary;
const normalizedPrimary = canonicalizeSpeechProviderId(primary, cfg) ?? primary;
const ordered = new Set<TtsProvider>([normalizedPrimary]);
for (const provider of TTS_PROVIDERS) {
if (provider !== normalizedPrimary) {
ordered.add(provider);
}
}
for (const provider of listSpeechProviders(cfg)) {
const normalized = normalizeSpeechProviderId(provider.id) ?? provider.id;
for (const provider of sortSpeechProvidersForAutoSelection(cfg)) {
const normalized = provider.id;
if (normalized !== normalizedPrimary) {
ordered.add(normalized);
}
@@ -574,7 +445,16 @@ export function isTtsProviderConfigured(
cfg?: OpenClawConfig,
): boolean {
const resolvedProvider = getSpeechProvider(provider, cfg);
return resolvedProvider?.isConfigured({ cfg, config }) ?? false;
if (!resolvedProvider) {
return false;
}
return (
resolvedProvider.isConfigured({
cfg,
providerConfig: getResolvedSpeechProviderConfig(config, resolvedProvider.id, cfg),
timeoutMs: config.timeoutMs,
}) ?? false
);
}
function formatTtsProviderError(provider: TtsProvider, err: unknown): string {
@@ -604,7 +484,18 @@ function resolveReadySpeechProvider(params: {
params.errors.push(`${params.provider}: no provider registered`);
return null;
}
if (!resolvedProvider.isConfigured({ cfg: params.cfg, config: params.config })) {
const providerConfig = getResolvedSpeechProviderConfig(
params.config,
resolvedProvider.id,
params.cfg,
);
if (
!resolvedProvider.isConfigured({
cfg: params.cfg,
providerConfig,
timeoutMs: params.config.timeoutMs,
})
) {
params.errors.push(`${params.provider}: not configured`);
return null;
}
@@ -638,7 +529,8 @@ function resolveTtsRequestSetup(params: {
}
const userProvider = getTtsProvider(config, prefsPath);
const provider = normalizeSpeechProviderId(params.providerOverride) ?? userProvider;
const provider =
canonicalizeSpeechProviderId(params.providerOverride, params.cfg) ?? userProvider;
return {
config,
providers: params.disableFallback ? [provider] : resolveTtsProviderOrder(provider, params.cfg),
@@ -715,9 +607,10 @@ export async function synthesizeSpeech(params: {
const synthesis = await resolvedProvider.synthesize({
text: params.text,
cfg: params.cfg,
config,
providerConfig: getResolvedSpeechProviderConfig(config, resolvedProvider.id, params.cfg),
target,
overrides: params.overrides,
providerOverrides: params.overrides?.providerOverrides?.[resolvedProvider.id],
timeoutMs: config.timeoutMs,
});
return {
success: true,
@@ -770,7 +663,8 @@ export async function textToSpeechTelephony(params: {
const synthesis = await resolvedProvider.synthesizeTelephony({
text: params.text,
cfg: params.cfg,
config,
providerConfig: getResolvedSpeechProviderConfig(config, resolvedProvider.id, params.cfg),
timeoutMs: config.timeoutMs,
});
return {
@@ -796,7 +690,7 @@ export async function listSpeechVoices(params: {
apiKey?: string;
baseUrl?: string;
}): Promise<SpeechVoiceOption[]> {
const provider = normalizeSpeechProviderId(params.provider);
const provider = canonicalizeSpeechProviderId(params.provider, params.cfg);
if (!provider) {
throw new Error("speech provider id is required");
}
@@ -813,7 +707,7 @@ export async function listSpeechVoices(params: {
}
return await resolvedProvider.listVoices({
cfg: params.cfg,
config,
providerConfig: getResolvedSpeechProviderConfig(config, resolvedProvider.id, params.cfg),
apiKey: params.apiKey,
baseUrl: params.baseUrl,
});
@@ -844,7 +738,10 @@ export async function maybeApplyTtsToPayload(params: {
const reply = resolveSendableOutboundReplyParts(params.payload);
const text = reply.text;
const directives = parseTtsDirectives(text, config.modelOverrides, config.openai.baseUrl);
const directives = parseTtsDirectives(text, config.modelOverrides, {
cfg: params.cfg,
providerConfigs: config.providerConfigs,
});
if (directives.warnings.length > 0) {
logVerbose(`TTS: ignored directive overrides (${directives.warnings.join("; ")})`);
}
@@ -971,15 +868,9 @@ export async function maybeApplyTtsToPayload(params: {
}
export const _test = {
isValidVoiceId,
isValidOpenAIVoice,
isValidOpenAIModel,
OPENAI_TTS_MODELS,
OPENAI_TTS_VOICES,
resolveOpenAITtsInstructions,
parseTtsDirectives,
resolveModelOverridePolicy,
summarizeText,
resolveOutputFormat,
resolveEdgeOutputFormat,
getResolvedSpeechProviderConfig,
};