From d6961c5d5c99d43379eaef8b5b1bfc03a4a630af Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Sun, 15 Mar 2026 23:05:03 +0000 Subject: [PATCH] Rebase: restore compatibility surfaces --- src/agents/model-selection.ts | 19 ++- src/auto-reply/status.ts | 2 +- src/commands/model-picker.ts | 2 +- src/extension-host/compat/plugin-api.ts | 3 +- .../embedding-runtime-backends.ts | 8 +- src/extension-host/provider-runtime.ts | 12 +- src/extension-host/runtime-registrations.ts | 11 +- src/extension-host/tool-runtime.ts | 13 +- src/extension-host/tts-runtime-execution.ts | 4 +- src/plugins/loader.ts | 120 ++---------------- src/plugins/registry.ts | 10 +- src/tts/tts.ts | 4 + 12 files changed, 76 insertions(+), 132 deletions(-) diff --git a/src/agents/model-selection.ts b/src/agents/model-selection.ts index 62233352bc9..b0ba0e4e437 100644 --- a/src/agents/model-selection.ts +++ b/src/agents/model-selection.ts @@ -14,8 +14,15 @@ import { } from "./agent-scope.js"; import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "./defaults.js"; import type { ModelCatalogEntry } from "./model-catalog.js"; -import { normalizeGoogleModelId } from "./model-id-normalization.js"; import { splitTrailingAuthProfile } from "./model-ref-profile.js"; +import { + legacyModelKey, + modelKey, + normalizeModelRef, + parseModelRef, + type ModelRef, +} from "./model-ref.js"; +import { normalizeProviderId, normalizeProviderIdForAuth } from "./provider-id.js"; const log = createSubsystemLogger("model-selection"); @@ -588,3 +595,13 @@ export function normalizeModelSelection(value: unknown): string | undefined { } return undefined; } + +export { + legacyModelKey, + modelKey, + normalizeModelRef, + normalizeProviderId, + normalizeProviderIdForAuth, + parseModelRef, +}; +export type { ModelRef }; diff --git a/src/auto-reply/status.ts b/src/auto-reply/status.ts index 902d81d8f84..7234ed785bf 100644 --- a/src/auto-reply/status.ts +++ b/src/auto-reply/status.ts @@ -799,7 +799,7 @@ type CommandsListItem = { function buildCommandItems( commands: ChatCommandDefinition[], - pluginCommands: ReturnType, + pluginCommands: ReturnType, ): CommandsListItem[] { const grouped = groupCommandsByCategory(commands); const items: CommandsListItem[] = []; diff --git a/src/commands/model-picker.ts b/src/commands/model-picker.ts index af7c0cee92b..64d9e533e1f 100644 --- a/src/commands/model-picker.ts +++ b/src/commands/model-picker.ts @@ -401,7 +401,7 @@ export async function promptDefaultModel( workspaceDir: params.workspaceDir, }); if (applied.defaultModel) { - await runExtensionHostProviderModelSelectedHook({ + await runProviderModelSelectedHook({ config: applied.config, model: applied.defaultModel, prompter: params.prompter, diff --git a/src/extension-host/compat/plugin-api.ts b/src/extension-host/compat/plugin-api.ts index 538d6702bb1..03a93374082 100644 --- a/src/extension-host/compat/plugin-api.ts +++ b/src/extension-host/compat/plugin-api.ts @@ -1,3 +1,4 @@ +import type { AnyAgentTool } from "../../agents/tools/common.js"; import type { PluginRecord } from "../../plugins/registry.js"; import type { PluginRuntime } from "../../plugins/runtime/types.js"; import type { @@ -31,7 +32,7 @@ export function createExtensionHostPluginApi(params: { config: OpenClawPluginApi["config"]; pluginConfig?: Record; registerTool: ( - tool: OpenClawPluginToolFactory | { name: string }, + tool: OpenClawPluginToolFactory | AnyAgentTool, opts?: { name?: string; names?: string[]; optional?: boolean }, ) => void; registerHook: ( diff --git a/src/extension-host/embedding-runtime-backends.ts b/src/extension-host/embedding-runtime-backends.ts index a0cdc8eb328..ee9ad0b47ac 100644 --- a/src/extension-host/embedding-runtime-backends.ts +++ b/src/extension-host/embedding-runtime-backends.ts @@ -24,7 +24,13 @@ export const EXTENSION_HOST_EMBEDDING_RUNTIME_BACKEND_IDS = [ export function isExtensionHostEmbeddingRuntimeBackendAutoSelectable( backendId: EmbeddingProviderId, ): boolean { - return backendId === "local" || EXTENSION_HOST_REMOTE_EMBEDDING_PROVIDER_IDS.includes(backendId); + return ( + backendId === "local" || + backendId === "openai" || + backendId === "gemini" || + backendId === "voyage" || + backendId === "mistral" + ); } export function resolveExtensionHostEmbeddingRuntimeDefaultModel( diff --git a/src/extension-host/provider-runtime.ts b/src/extension-host/provider-runtime.ts index 657765f3866..a85908d92e9 100644 --- a/src/extension-host/provider-runtime.ts +++ b/src/extension-host/provider-runtime.ts @@ -3,7 +3,17 @@ import type { ProviderPlugin } from "../plugins/types.js"; import { listExtensionHostProviderRegistrations } from "./runtime-registry.js"; export function resolveExtensionHostProviders(params: { - registry: Pick; + registry: Pick< + PluginRegistry, + | "channels" + | "tools" + | "providers" + | "cliRegistrars" + | "commands" + | "services" + | "httpRoutes" + | "gatewayHandlers" + >; }): ProviderPlugin[] { return listExtensionHostProviderRegistrations(params.registry).map((entry) => ({ ...entry.provider, diff --git a/src/extension-host/runtime-registrations.ts b/src/extension-host/runtime-registrations.ts index cd7342f4466..e37882a5bc5 100644 --- a/src/extension-host/runtime-registrations.ts +++ b/src/extension-host/runtime-registrations.ts @@ -110,10 +110,13 @@ export function resolveExtensionToolRegistration(params: { names.push(params.tool.name); } const normalizedNames = normalizeNameList(names); - const factory: OpenClawPluginToolFactory = - typeof params.tool === "function" - ? params.tool - : (_ctx: OpenClawPluginToolContext) => params.tool; + let factory: OpenClawPluginToolFactory; + if (typeof params.tool === "function") { + factory = params.tool; + } else { + const tool = params.tool; + factory = (_ctx: OpenClawPluginToolContext) => tool; + } return { names: normalizedNames, diff --git a/src/extension-host/tool-runtime.ts b/src/extension-host/tool-runtime.ts index 226a6552ecb..e4d410334bf 100644 --- a/src/extension-host/tool-runtime.ts +++ b/src/extension-host/tool-runtime.ts @@ -44,7 +44,18 @@ function isOptionalToolAllowed(params: { } export function resolveExtensionHostPluginTools(params: { - registry: Pick; + registry: Pick< + PluginRegistry, + | "channels" + | "tools" + | "providers" + | "cliRegistrars" + | "commands" + | "services" + | "httpRoutes" + | "gatewayHandlers" + | "diagnostics" + >; context: OpenClawPluginToolContext; existingToolNames?: Set; toolAllowlist?: string[]; diff --git a/src/extension-host/tts-runtime-execution.ts b/src/extension-host/tts-runtime-execution.ts index 1b60e39f99e..7460568e86d 100644 --- a/src/extension-host/tts-runtime-execution.ts +++ b/src/extension-host/tts-runtime-execution.ts @@ -18,7 +18,7 @@ import { supportsExtensionHostTtsTelephony, } from "./tts-runtime-registry.js"; -const TELEGRAM_OUTPUT = { +const TELEGRAM_OUTPUT: ExtensionHostTtsOutputFormat = { openai: "opus" as const, // ElevenLabs output formats use codec_sample_rate_bitrate naming. // Opus @ 48kHz/64kbps is a good voice-note tradeoff for Telegram. @@ -27,7 +27,7 @@ const TELEGRAM_OUTPUT = { voiceCompatible: true, }; -const DEFAULT_OUTPUT = { +const DEFAULT_OUTPUT: ExtensionHostTtsOutputFormat = { openai: "mp3" as const, elevenlabs: "mp3_44100_128", extension: ".mp3", diff --git a/src/plugins/loader.ts b/src/plugins/loader.ts index 478f4365b4a..5b163fc786b 100644 --- a/src/plugins/loader.ts +++ b/src/plugins/loader.ts @@ -1,3 +1,7 @@ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { MAX_EXTENSION_HOST_REGISTRY_CACHE_ENTRIES } from "../extension-host/activation/loader-cache.js"; import { listPluginSdkAliasCandidates, listPluginSdkExportedSubpaths, @@ -33,79 +37,18 @@ import type { export type PluginLoadResult = PluginRegistry; -export type PluginLoadOptions = ExtensionHostPluginLoadOptions; +export type PluginLoadOptions = + import("../extension-host/activation/loader-orchestrator.js").ExtensionHostPluginLoadOptions; export function clearPluginLoaderCache(): void { clearExtensionHostLoaderState(); } -const defaultLogger = () => createSubsystemLogger("plugins"); - -type PluginSdkAliasCandidateKind = "dist" | "src"; - -function resolvePluginSdkAliasCandidateOrder(params: { - modulePath: string; - isProduction: boolean; -}): PluginSdkAliasCandidateKind[] { - const normalizedModulePath = params.modulePath.replace(/\\/g, "/"); - const isDistRuntime = normalizedModulePath.includes("/dist/"); - return isDistRuntime || params.isProduction ? ["dist", "src"] : ["src", "dist"]; +export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegistry { + return loadExtensionHostPluginRegistry(options); } -function listPluginSdkAliasCandidates(params: { - srcFile: string; - distFile: string; - modulePath: string; -}) { - const orderedKinds = resolvePluginSdkAliasCandidateOrder({ - modulePath: params.modulePath, - isProduction: process.env.NODE_ENV === "production", - }); - let cursor = path.dirname(params.modulePath); - const candidates: string[] = []; - for (let i = 0; i < 6; i += 1) { - const candidateMap = { - src: path.join(cursor, "src", "plugin-sdk", params.srcFile), - dist: path.join(cursor, "dist", "plugin-sdk", params.distFile), - } as const; - for (const kind of orderedKinds) { - candidates.push(candidateMap[kind]); - } - const parent = path.dirname(cursor); - if (parent === cursor) { - break; - } - cursor = parent; - } - return candidates; -} - -const resolvePluginSdkAliasFile = (params: { - srcFile: string; - distFile: string; - modulePath?: string; -}): string | null => { - try { - const modulePath = params.modulePath ?? fileURLToPath(import.meta.url); - for (const candidate of listPluginSdkAliasCandidates({ - srcFile: params.srcFile, - distFile: params.distFile, - modulePath, - })) { - if (fs.existsSync(candidate)) { - return candidate; - } - } - } catch { - // ignore - } - return null; -}; - -const resolvePluginSdkAlias = (): string | null => - resolvePluginSdkAliasFile({ srcFile: "root-alias.cjs", distFile: "root-alias.cjs" }); - -const resolveExtensionApiAlias = (params: { modulePath?: string } = {}): string | null => { +function resolveExtensionApiAlias(params: { modulePath?: string } = {}): string | null { try { const modulePath = params.modulePath ?? fileURLToPath(import.meta.url); const packageRoot = resolveOpenClawPackageRootSync({ @@ -133,53 +76,8 @@ const resolveExtensionApiAlias = (params: { modulePath?: string } = {}): string // ignore } return null; -}; - -const cachedPluginSdkExportedSubpaths = new Map(); - -function listPluginSdkExportedSubpaths(params: { modulePath?: string } = {}): string[] { - const modulePath = params.modulePath ?? fileURLToPath(import.meta.url); - const packageRoot = resolveOpenClawPackageRootSync({ - cwd: path.dirname(modulePath), - }); - if (!packageRoot) { - return []; - } - const cached = cachedPluginSdkExportedSubpaths.get(packageRoot); - if (cached) { - return cached; - } - try { - const pkgRaw = fs.readFileSync(path.join(packageRoot, "package.json"), "utf-8"); - const pkg = JSON.parse(pkgRaw) as { - exports?: Record; - }; - const subpaths = Object.keys(pkg.exports ?? {}) - .filter((key) => key.startsWith("./plugin-sdk/")) - .map((key) => key.slice("./plugin-sdk/".length)) - .filter((subpath) => Boolean(subpath) && !subpath.includes("/")) - .toSorted(); - cachedPluginSdkExportedSubpaths.set(packageRoot, subpaths); - return subpaths; - } catch { - return []; - } } -const resolvePluginSdkScopedAliasMap = (): Record => { - const aliasMap: Record = {}; - for (const subpath of listPluginSdkExportedSubpaths()) { - const resolved = resolvePluginSdkAliasFile({ - srcFile: `${subpath}.ts`, - distFile: `${subpath}.js`, - }); - if (resolved) { - aliasMap[`openclaw/plugin-sdk/${subpath}`] = resolved; - } - } - return aliasMap; -}; - export const __testing = { listPluginSdkAliasCandidates, listPluginSdkExportedSubpaths, diff --git a/src/plugins/registry.ts b/src/plugins/registry.ts index e36c9b10f76..2a2a4ff20cf 100644 --- a/src/plugins/registry.ts +++ b/src/plugins/registry.ts @@ -12,18 +12,12 @@ import { findOverlappingPluginHttpRoute } from "./http-route-overlap.js"; import { registerPluginInteractiveHandler } from "./interactive.js"; import { normalizeRegisteredProvider } from "./provider-validation.js"; import type { PluginRuntime } from "./runtime/types.js"; -import { defaultSlotIdForKey } from "./slots.js"; -import { - isPromptInjectionHookName, - stripPromptMutationFieldsFromLegacyHookResult, -} from "./types.js"; import type { OpenClawPluginCliRegistrar, OpenClawPluginCommandDefinition, OpenClawPluginHttpRouteAuth, - OpenClawPluginHttpRouteMatch, OpenClawPluginHttpRouteHandler, - ProviderPlugin, + OpenClawPluginHttpRouteMatch, OpenClawPluginService, OpenClawPluginToolFactory, PluginConfigUiHint, @@ -32,8 +26,8 @@ import type { PluginFormat, PluginLogger, PluginOrigin, - PluginKind, PluginHookRegistration as TypedPluginHookRegistration, + ProviderPlugin, } from "./types.js"; export type PluginToolRegistration = { diff --git a/src/tts/tts.ts b/src/tts/tts.ts index 51692a9dfcd..219ffdb9ce4 100644 --- a/src/tts/tts.ts +++ b/src/tts/tts.ts @@ -21,6 +21,7 @@ import { import { getExtensionHostTtsMaxLength, isExtensionHostTtsEnabled, + resolveExtensionHostTtsAutoMode, isExtensionHostTtsSummarizationEnabled, resolveExtensionHostTtsPrefsPath, setExtensionHostTtsAutoMode, @@ -49,6 +50,7 @@ import { summarizeText, } from "./tts-core.js"; export { OPENAI_TTS_MODELS, OPENAI_TTS_VOICES } from "./tts-core.js"; +export type { ResolvedTtsConfig } from "../extension-host/tts-config.js"; export type TtsDirectiveOverrides = { ttsText?: string; @@ -103,6 +105,8 @@ export const resolveTtsConfig = resolveExtensionHostTtsConfig; export const resolveTtsPrefsPath = resolveExtensionHostTtsPrefsPath; +export const resolveTtsAutoMode = resolveExtensionHostTtsAutoMode; + export const buildTtsSystemPromptHint = buildExtensionHostTtsSystemPromptHint; export const isTtsEnabled = isExtensionHostTtsEnabled;