feat(plugins): add speech provider registration

This commit is contained in:
Peter Steinberger
2026-03-16 18:49:55 -07:00
parent ad05cd9ab2
commit 662031a88e
35 changed files with 658 additions and 286 deletions

View File

@@ -494,6 +494,7 @@ function createPluginRecord(params: {
hookNames: [],
channelIds: [],
providerIds: [],
speechProviderIds: [],
webSearchProviderIds: [],
gatewayMethods: [],
cliCommands: [],

View File

@@ -46,6 +46,7 @@ import type {
PluginHookName,
PluginHookHandlerMap,
PluginHookRegistration as TypedPluginHookRegistration,
SpeechProviderPlugin,
WebSearchProviderPlugin,
} from "./types.js";
@@ -110,6 +111,14 @@ export type PluginWebSearchProviderRegistration = {
rootDir?: string;
};
export type PluginSpeechProviderRegistration = {
pluginId: string;
pluginName?: string;
provider: SpeechProviderPlugin;
source: string;
rootDir?: string;
};
export type PluginHookRegistration = {
pluginId: string;
entry: HookEntry;
@@ -154,6 +163,7 @@ export type PluginRecord = {
hookNames: string[];
channelIds: string[];
providerIds: string[];
speechProviderIds: string[];
webSearchProviderIds: string[];
gatewayMethods: string[];
cliCommands: string[];
@@ -174,6 +184,7 @@ export type PluginRegistry = {
channels: PluginChannelRegistration[];
channelSetups: PluginChannelSetupRegistration[];
providers: PluginProviderRegistration[];
speechProviders: PluginSpeechProviderRegistration[];
webSearchProviders: PluginWebSearchProviderRegistration[];
gatewayHandlers: GatewayRequestHandlers;
httpRoutes: PluginHttpRouteRegistration[];
@@ -219,6 +230,7 @@ export function createEmptyPluginRegistry(): PluginRegistry {
channels: [],
channelSetups: [],
providers: [],
speechProviders: [],
webSearchProviders: [],
gatewayHandlers: {},
httpRoutes: [],
@@ -550,6 +562,37 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
});
};
const registerSpeechProvider = (record: PluginRecord, provider: SpeechProviderPlugin) => {
const id = provider.id.trim();
if (!id) {
pushDiagnostic({
level: "error",
pluginId: record.id,
source: record.source,
message: "speech provider registration missing id",
});
return;
}
const existing = registry.speechProviders.find((entry) => entry.provider.id === id);
if (existing) {
pushDiagnostic({
level: "error",
pluginId: record.id,
source: record.source,
message: `speech provider already registered: ${id} (${existing.pluginId})`,
});
return;
}
record.speechProviderIds.push(id);
registry.speechProviders.push({
pluginId: record.id,
pluginName: record.name,
provider,
source: record.source,
rootDir: record.rootDir,
});
};
const registerWebSearchProvider = (record: PluginRecord, provider: WebSearchProviderPlugin) => {
const id = provider.id.trim();
if (!id) {
@@ -789,6 +832,10 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
registerChannel: (registration) => registerChannel(record, registration, registrationMode),
registerProvider:
registrationMode === "full" ? (provider) => registerProvider(record, provider) : () => {},
registerSpeechProvider:
registrationMode === "full"
? (provider) => registerSpeechProvider(record, provider)
: () => {},
registerWebSearchProvider:
registrationMode === "full"
? (provider) => registerWebSearchProvider(record, provider)
@@ -862,6 +909,7 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
registerTool,
registerChannel,
registerProvider,
registerSpeechProvider,
registerWebSearchProvider,
registerGatewayMethod,
registerCli,

View File

@@ -27,6 +27,14 @@ import type { HookEntry } from "../hooks/types.js";
import type { ProviderUsageSnapshot } from "../infra/provider-usage.types.js";
import type { RuntimeEnv } from "../runtime.js";
import type { RuntimeWebSearchMetadata } from "../secrets/runtime-web-tools.types.js";
import type {
SpeechProviderConfiguredContext,
SpeechProviderId,
SpeechSynthesisRequest,
SpeechSynthesisResult,
SpeechTelephonySynthesisRequest,
SpeechTelephonySynthesisResult,
} from "../tts/provider-types.js";
import type { WizardPrompter } from "../wizard/prompts.js";
import type { PluginRuntime } from "./runtime/types.js";
@@ -853,6 +861,23 @@ export type PluginWebSearchProviderEntry = WebSearchProviderPlugin & {
pluginId: string;
};
export type SpeechProviderPlugin = {
id: SpeechProviderId;
label: string;
aliases?: string[];
models?: readonly string[];
voices?: readonly string[];
isConfigured: (ctx: SpeechProviderConfiguredContext) => boolean;
synthesize: (req: SpeechSynthesisRequest) => Promise<SpeechSynthesisResult>;
synthesizeTelephony?: (
req: SpeechTelephonySynthesisRequest,
) => Promise<SpeechTelephonySynthesisResult>;
};
export type PluginSpeechProviderEntry = SpeechProviderPlugin & {
pluginId: string;
};
export type OpenClawPluginGatewayMethod = {
method: string;
handler: GatewayRequestHandler;
@@ -1211,6 +1236,7 @@ export type OpenClawPluginApi = {
registerCli: (registrar: OpenClawPluginCliRegistrar, opts?: { commands?: string[] }) => void;
registerService: (service: OpenClawPluginService) => void;
registerProvider: (provider: ProviderPlugin) => void;
registerSpeechProvider: (provider: SpeechProviderPlugin) => void;
registerWebSearchProvider: (provider: WebSearchProviderPlugin) => void;
registerInteractiveHandler: (registration: PluginInteractiveHandlerRegistration) => void;
/**