Rebase: restore compatibility surfaces

This commit is contained in:
Gustavo Madeira Santana
2026-03-15 23:05:03 +00:00
parent 07436df259
commit d6961c5d5c
12 changed files with 76 additions and 132 deletions

View File

@@ -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 };

View File

@@ -799,7 +799,7 @@ type CommandsListItem = {
function buildCommandItems(
commands: ChatCommandDefinition[],
pluginCommands: ReturnType<typeof listPluginCommands>,
pluginCommands: ReturnType<typeof listExtensionHostPluginCommands>,
): CommandsListItem[] {
const grouped = groupCommandsByCategory(commands);
const items: CommandsListItem[] = [];

View File

@@ -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,

View File

@@ -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<string, unknown>;
registerTool: (
tool: OpenClawPluginToolFactory | { name: string },
tool: OpenClawPluginToolFactory | AnyAgentTool,
opts?: { name?: string; names?: string[]; optional?: boolean },
) => void;
registerHook: (

View File

@@ -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(

View File

@@ -3,7 +3,17 @@ import type { ProviderPlugin } from "../plugins/types.js";
import { listExtensionHostProviderRegistrations } from "./runtime-registry.js";
export function resolveExtensionHostProviders(params: {
registry: Pick<PluginRegistry, "providers">;
registry: Pick<
PluginRegistry,
| "channels"
| "tools"
| "providers"
| "cliRegistrars"
| "commands"
| "services"
| "httpRoutes"
| "gatewayHandlers"
>;
}): ProviderPlugin[] {
return listExtensionHostProviderRegistrations(params.registry).map((entry) => ({
...entry.provider,

View File

@@ -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,

View File

@@ -44,7 +44,18 @@ function isOptionalToolAllowed(params: {
}
export function resolveExtensionHostPluginTools(params: {
registry: Pick<PluginRegistry, "tools" | "diagnostics">;
registry: Pick<
PluginRegistry,
| "channels"
| "tools"
| "providers"
| "cliRegistrars"
| "commands"
| "services"
| "httpRoutes"
| "gatewayHandlers"
| "diagnostics"
>;
context: OpenClawPluginToolContext;
existingToolNames?: Set<string>;
toolAllowlist?: string[];

View File

@@ -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",

View File

@@ -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<string, string[]>();
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<string, unknown>;
};
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<string, string> => {
const aliasMap: Record<string, string> = {};
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,

View File

@@ -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 = {

View File

@@ -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;