mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-28 10:22:32 +00:00
refactor: make memory embedding adapters generic
This commit is contained in:
@@ -12,7 +12,10 @@ import {
|
||||
type MemoryEmbeddingProviderCreateOptions,
|
||||
type MemoryEmbeddingProviderRuntime,
|
||||
} from "../engine-host-api.js";
|
||||
import { canAutoSelectLocal } from "./provider-adapters.js";
|
||||
import {
|
||||
canAutoSelectLocal,
|
||||
getBuiltinMemoryEmbeddingProviderAdapter,
|
||||
} from "./provider-adapters.js";
|
||||
|
||||
export {
|
||||
DEFAULT_GEMINI_EMBEDDING_MODEL,
|
||||
@@ -92,6 +95,15 @@ function resolveProviderModel(
|
||||
return adapter.defaultModel ?? "";
|
||||
}
|
||||
|
||||
export function resolveEmbeddingProviderFallbackModel(
|
||||
providerId: string,
|
||||
fallbackSourceModel: string,
|
||||
): string {
|
||||
const adapter =
|
||||
getMemoryEmbeddingProvider(providerId) ?? getBuiltinMemoryEmbeddingProviderAdapter(providerId);
|
||||
return adapter?.defaultModel ?? fallbackSourceModel;
|
||||
}
|
||||
|
||||
async function createWithAdapter(
|
||||
adapter: MemoryEmbeddingProviderAdapter,
|
||||
options: CreateEmbeddingProviderOptions,
|
||||
|
||||
@@ -32,14 +32,10 @@ import {
|
||||
} from "../engine-host-api.js";
|
||||
import {
|
||||
createEmbeddingProvider,
|
||||
DEFAULT_GEMINI_EMBEDDING_MODEL,
|
||||
DEFAULT_MISTRAL_EMBEDDING_MODEL,
|
||||
DEFAULT_OLLAMA_EMBEDDING_MODEL,
|
||||
DEFAULT_OPENAI_EMBEDDING_MODEL,
|
||||
DEFAULT_VOYAGE_EMBEDDING_MODEL,
|
||||
type EmbeddingProvider,
|
||||
type EmbeddingProviderId,
|
||||
type EmbeddingProviderRuntime,
|
||||
resolveEmbeddingProviderFallbackModel,
|
||||
} from "./embeddings.js";
|
||||
import { buildFileEntry } from "./internal.js";
|
||||
import { loadSqliteVecExtension } from "./sqlite-vec.js";
|
||||
@@ -1119,18 +1115,7 @@ export abstract class MemoryManagerSyncOps {
|
||||
}
|
||||
const fallbackFrom = this.provider.id as EmbeddingProviderId;
|
||||
|
||||
const fallbackModel =
|
||||
fallback === "gemini"
|
||||
? DEFAULT_GEMINI_EMBEDDING_MODEL
|
||||
: fallback === "openai"
|
||||
? DEFAULT_OPENAI_EMBEDDING_MODEL
|
||||
: fallback === "voyage"
|
||||
? DEFAULT_VOYAGE_EMBEDDING_MODEL
|
||||
: fallback === "mistral"
|
||||
? DEFAULT_MISTRAL_EMBEDDING_MODEL
|
||||
: fallback === "ollama"
|
||||
? DEFAULT_OLLAMA_EMBEDDING_MODEL
|
||||
: this.settings.model;
|
||||
const fallbackModel = resolveEmbeddingProviderFallbackModel(fallback, this.settings.model);
|
||||
|
||||
const fallbackResult = await createEmbeddingProvider({
|
||||
config: this.cfg,
|
||||
|
||||
@@ -334,6 +334,16 @@ export const builtinMemoryEmbeddingProviderAdapters = [
|
||||
ollamaAdapter,
|
||||
] as const;
|
||||
|
||||
const builtinMemoryEmbeddingProviderAdapterById = new Map(
|
||||
builtinMemoryEmbeddingProviderAdapters.map((adapter) => [adapter.id, adapter]),
|
||||
);
|
||||
|
||||
export function getBuiltinMemoryEmbeddingProviderAdapter(
|
||||
id: string,
|
||||
): MemoryEmbeddingProviderAdapter | undefined {
|
||||
return builtinMemoryEmbeddingProviderAdapterById.get(id);
|
||||
}
|
||||
|
||||
export function registerBuiltInMemoryEmbeddingProviders(register: {
|
||||
registerMemoryEmbeddingProvider: (adapter: MemoryEmbeddingProviderAdapter) => void;
|
||||
}): void {
|
||||
|
||||
@@ -420,6 +420,10 @@
|
||||
"types": "./dist/plugin-sdk/memory-core.d.ts",
|
||||
"default": "./dist/plugin-sdk/memory-core.js"
|
||||
},
|
||||
"./plugin-sdk/memory-core-engine-runtime": {
|
||||
"types": "./dist/plugin-sdk/memory-core-engine-runtime.d.ts",
|
||||
"default": "./dist/plugin-sdk/memory-core-engine-runtime.js"
|
||||
},
|
||||
"./plugin-sdk/memory-core-host": {
|
||||
"types": "./dist/plugin-sdk/memory-core-host.d.ts",
|
||||
"default": "./dist/plugin-sdk/memory-core-host.js"
|
||||
|
||||
@@ -95,6 +95,7 @@
|
||||
"matrix",
|
||||
"mattermost",
|
||||
"memory-core",
|
||||
"memory-core-engine-runtime",
|
||||
"memory-core-host",
|
||||
"memory-core-host-engine",
|
||||
"memory-core-host-runtime",
|
||||
|
||||
7
src/plugin-sdk/memory-core-engine-runtime.ts
Normal file
7
src/plugin-sdk/memory-core-engine-runtime.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
// Thin engine runtime compat surface for the bundled memory-core plugin.
|
||||
// Keep extension-owned engine exports isolated behind a dedicated SDK subpath.
|
||||
|
||||
export {
|
||||
getMemorySearchManager,
|
||||
MemoryIndexManager,
|
||||
} from "../../extensions/memory-core/src/memory/index.js";
|
||||
@@ -14,10 +14,7 @@ export { SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js";
|
||||
export { loadConfig } from "../config/config.js";
|
||||
export { resolveStateDir } from "../config/paths.js";
|
||||
export { resolveSessionTranscriptsDirForAgent } from "../config/sessions/paths.js";
|
||||
export {
|
||||
getMemorySearchManager,
|
||||
MemoryIndexManager,
|
||||
} from "../../extensions/memory-core/src/memory/index.js";
|
||||
export { getMemorySearchManager, MemoryIndexManager } from "./memory-core-engine-runtime.js";
|
||||
export {
|
||||
listMemoryFiles,
|
||||
normalizeExtraMemoryPaths,
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { getRegisteredMemoryEmbeddingProvider } from "../memory-embedding-providers.js";
|
||||
import { createPluginRegistry, type PluginRecord } from "../registry.js";
|
||||
import type { PluginRuntime } from "../runtime/types.js";
|
||||
import { createPluginRecord } from "../status.test-helpers.js";
|
||||
import type { OpenClawPluginApi } from "../types.js";
|
||||
|
||||
function registerTestPlugin(params: {
|
||||
registry: ReturnType<typeof createPluginRegistry>;
|
||||
config: OpenClawConfig;
|
||||
record: PluginRecord;
|
||||
register(api: OpenClawPluginApi): void;
|
||||
}) {
|
||||
params.registry.registry.plugins.push(params.record);
|
||||
params.register(
|
||||
params.registry.createApi(params.record, {
|
||||
config: params.config,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
describe("memory embedding provider registration", () => {
|
||||
it("only allows memory plugins to register adapters", () => {
|
||||
const config = {} as OpenClawConfig;
|
||||
const registry = createPluginRegistry({
|
||||
logger: {
|
||||
info() {},
|
||||
warn() {},
|
||||
error() {},
|
||||
debug() {},
|
||||
},
|
||||
runtime: {} as PluginRuntime,
|
||||
});
|
||||
|
||||
registerTestPlugin({
|
||||
registry,
|
||||
config,
|
||||
record: createPluginRecord({
|
||||
id: "not-memory",
|
||||
name: "Not Memory",
|
||||
source: "/virtual/not-memory/index.ts",
|
||||
}),
|
||||
register(api) {
|
||||
api.registerMemoryEmbeddingProvider({
|
||||
id: "forbidden",
|
||||
create: async () => ({ provider: null }),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
expect(getRegisteredMemoryEmbeddingProvider("forbidden")).toBeUndefined();
|
||||
expect(registry.registry.diagnostics).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
pluginId: "not-memory",
|
||||
message: "only memory plugins can register memory embedding providers",
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("records the owning memory plugin id for registered adapters", () => {
|
||||
const config = {} as OpenClawConfig;
|
||||
const registry = createPluginRegistry({
|
||||
logger: {
|
||||
info() {},
|
||||
warn() {},
|
||||
error() {},
|
||||
debug() {},
|
||||
},
|
||||
runtime: {} as PluginRuntime,
|
||||
});
|
||||
|
||||
registerTestPlugin({
|
||||
registry,
|
||||
config,
|
||||
record: createPluginRecord({
|
||||
id: "memory-core",
|
||||
name: "Memory Core",
|
||||
kind: "memory",
|
||||
source: "/virtual/memory-core/index.ts",
|
||||
}),
|
||||
register(api) {
|
||||
api.registerMemoryEmbeddingProvider({
|
||||
id: "openai",
|
||||
create: async () => ({ provider: null }),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
expect(getRegisteredMemoryEmbeddingProvider("openai")).toEqual({
|
||||
adapter: expect.objectContaining({ id: "openai" }),
|
||||
ownerPluginId: "memory-core",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -24,8 +24,8 @@ import { clearPluginInteractiveHandlers } from "./interactive.js";
|
||||
import { loadPluginManifestRegistry } from "./manifest-registry.js";
|
||||
import {
|
||||
clearMemoryEmbeddingProviders,
|
||||
listMemoryEmbeddingProviders,
|
||||
restoreMemoryEmbeddingProviders,
|
||||
listRegisteredMemoryEmbeddingProviders,
|
||||
restoreRegisteredMemoryEmbeddingProviders,
|
||||
} from "./memory-embedding-providers.js";
|
||||
import {
|
||||
clearMemoryPluginState,
|
||||
@@ -104,7 +104,7 @@ export class PluginLoadFailureError extends Error {
|
||||
|
||||
type CachedPluginState = {
|
||||
registry: PluginRegistry;
|
||||
memoryEmbeddingProviders: ReturnType<typeof listMemoryEmbeddingProviders>;
|
||||
memoryEmbeddingProviders: ReturnType<typeof listRegisteredMemoryEmbeddingProviders>;
|
||||
memoryFlushPlanResolver: ReturnType<typeof getMemoryFlushPlanResolver>;
|
||||
memoryPromptBuilder: ReturnType<typeof getMemoryPromptSectionBuilder>;
|
||||
memoryRuntime: ReturnType<typeof getMemoryRuntime>;
|
||||
@@ -719,7 +719,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
if (cacheEnabled) {
|
||||
const cached = getCachedPluginRegistry(cacheKey);
|
||||
if (cached) {
|
||||
restoreMemoryEmbeddingProviders(cached.memoryEmbeddingProviders);
|
||||
restoreRegisteredMemoryEmbeddingProviders(cached.memoryEmbeddingProviders);
|
||||
restoreMemoryPluginState({
|
||||
promptBuilder: cached.memoryPromptBuilder,
|
||||
flushPlanResolver: cached.memoryFlushPlanResolver,
|
||||
@@ -1235,7 +1235,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
hookPolicy: entry?.hooks,
|
||||
registrationMode,
|
||||
});
|
||||
const previousMemoryEmbeddingProviders = listMemoryEmbeddingProviders();
|
||||
const previousMemoryEmbeddingProviders = listRegisteredMemoryEmbeddingProviders();
|
||||
const previousMemoryFlushPlanResolver = getMemoryFlushPlanResolver();
|
||||
const previousMemoryPromptBuilder = getMemoryPromptSectionBuilder();
|
||||
const previousMemoryRuntime = getMemoryRuntime();
|
||||
@@ -1252,7 +1252,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
}
|
||||
// Snapshot loads should not replace process-global runtime prompt state.
|
||||
if (!shouldActivate) {
|
||||
restoreMemoryEmbeddingProviders(previousMemoryEmbeddingProviders);
|
||||
restoreRegisteredMemoryEmbeddingProviders(previousMemoryEmbeddingProviders);
|
||||
restoreMemoryPluginState({
|
||||
promptBuilder: previousMemoryPromptBuilder,
|
||||
flushPlanResolver: previousMemoryFlushPlanResolver,
|
||||
@@ -1262,7 +1262,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
registry.plugins.push(record);
|
||||
seenIds.set(pluginId, candidate.origin);
|
||||
} catch (err) {
|
||||
restoreMemoryEmbeddingProviders(previousMemoryEmbeddingProviders);
|
||||
restoreRegisteredMemoryEmbeddingProviders(previousMemoryEmbeddingProviders);
|
||||
restoreMemoryPluginState({
|
||||
promptBuilder: previousMemoryPromptBuilder,
|
||||
flushPlanResolver: previousMemoryFlushPlanResolver,
|
||||
@@ -1303,7 +1303,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
if (cacheEnabled) {
|
||||
setCachedPluginRegistry(cacheKey, {
|
||||
registry,
|
||||
memoryEmbeddingProviders: listMemoryEmbeddingProviders(),
|
||||
memoryEmbeddingProviders: listRegisteredMemoryEmbeddingProviders(),
|
||||
memoryFlushPlanResolver: getMemoryFlushPlanResolver(),
|
||||
memoryPromptBuilder: getMemoryPromptSectionBuilder(),
|
||||
memoryRuntime: getMemoryRuntime(),
|
||||
|
||||
@@ -2,8 +2,11 @@ import { afterEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
clearMemoryEmbeddingProviders,
|
||||
getMemoryEmbeddingProvider,
|
||||
getRegisteredMemoryEmbeddingProvider,
|
||||
listMemoryEmbeddingProviders,
|
||||
listRegisteredMemoryEmbeddingProviders,
|
||||
registerMemoryEmbeddingProvider,
|
||||
restoreRegisteredMemoryEmbeddingProviders,
|
||||
restoreMemoryEmbeddingProviders,
|
||||
type MemoryEmbeddingProviderAdapter,
|
||||
} from "./memory-embedding-providers.js";
|
||||
@@ -39,6 +42,38 @@ describe("memory embedding provider registry", () => {
|
||||
expect(getMemoryEmbeddingProvider("beta")).toBe(beta);
|
||||
});
|
||||
|
||||
it("tracks owner plugin ids in registered snapshots", () => {
|
||||
const alpha = createAdapter("alpha");
|
||||
registerMemoryEmbeddingProvider(alpha, { ownerPluginId: "memory-core" });
|
||||
|
||||
expect(getRegisteredMemoryEmbeddingProvider("alpha")).toEqual({
|
||||
adapter: alpha,
|
||||
ownerPluginId: "memory-core",
|
||||
});
|
||||
expect(listRegisteredMemoryEmbeddingProviders()).toEqual([
|
||||
{
|
||||
adapter: alpha,
|
||||
ownerPluginId: "memory-core",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("restores registered snapshots with owner metadata", () => {
|
||||
const beta = createAdapter("beta");
|
||||
|
||||
restoreRegisteredMemoryEmbeddingProviders([
|
||||
{
|
||||
adapter: beta,
|
||||
ownerPluginId: "memory-core",
|
||||
},
|
||||
]);
|
||||
|
||||
expect(getRegisteredMemoryEmbeddingProvider("beta")).toEqual({
|
||||
adapter: beta,
|
||||
ownerPluginId: "memory-core",
|
||||
});
|
||||
});
|
||||
|
||||
it("clears the registry", () => {
|
||||
registerMemoryEmbeddingProvider(createAdapter("alpha"));
|
||||
|
||||
|
||||
@@ -67,24 +67,56 @@ export type MemoryEmbeddingProviderAdapter = {
|
||||
shouldContinueAutoSelection?: (err: unknown) => boolean;
|
||||
};
|
||||
|
||||
const memoryEmbeddingProviders = new Map<string, MemoryEmbeddingProviderAdapter>();
|
||||
export type RegisteredMemoryEmbeddingProvider = {
|
||||
adapter: MemoryEmbeddingProviderAdapter;
|
||||
ownerPluginId?: string;
|
||||
};
|
||||
|
||||
export function registerMemoryEmbeddingProvider(adapter: MemoryEmbeddingProviderAdapter): void {
|
||||
memoryEmbeddingProviders.set(adapter.id, adapter);
|
||||
const memoryEmbeddingProviders = new Map<string, RegisteredMemoryEmbeddingProvider>();
|
||||
|
||||
export function registerMemoryEmbeddingProvider(
|
||||
adapter: MemoryEmbeddingProviderAdapter,
|
||||
options?: { ownerPluginId?: string },
|
||||
): void {
|
||||
memoryEmbeddingProviders.set(adapter.id, {
|
||||
adapter,
|
||||
ownerPluginId: options?.ownerPluginId,
|
||||
});
|
||||
}
|
||||
|
||||
export function getMemoryEmbeddingProvider(id: string): MemoryEmbeddingProviderAdapter | undefined {
|
||||
export function getRegisteredMemoryEmbeddingProvider(
|
||||
id: string,
|
||||
): RegisteredMemoryEmbeddingProvider | undefined {
|
||||
return memoryEmbeddingProviders.get(id);
|
||||
}
|
||||
|
||||
export function listMemoryEmbeddingProviders(): MemoryEmbeddingProviderAdapter[] {
|
||||
export function getMemoryEmbeddingProvider(id: string): MemoryEmbeddingProviderAdapter | undefined {
|
||||
return memoryEmbeddingProviders.get(id)?.adapter;
|
||||
}
|
||||
|
||||
export function listRegisteredMemoryEmbeddingProviders(): RegisteredMemoryEmbeddingProvider[] {
|
||||
return Array.from(memoryEmbeddingProviders.values());
|
||||
}
|
||||
|
||||
export function listMemoryEmbeddingProviders(): MemoryEmbeddingProviderAdapter[] {
|
||||
return listRegisteredMemoryEmbeddingProviders().map((entry) => entry.adapter);
|
||||
}
|
||||
|
||||
export function restoreMemoryEmbeddingProviders(adapters: MemoryEmbeddingProviderAdapter[]): void {
|
||||
memoryEmbeddingProviders.clear();
|
||||
for (const adapter of adapters) {
|
||||
memoryEmbeddingProviders.set(adapter.id, adapter);
|
||||
registerMemoryEmbeddingProvider(adapter);
|
||||
}
|
||||
}
|
||||
|
||||
export function restoreRegisteredMemoryEmbeddingProviders(
|
||||
entries: RegisteredMemoryEmbeddingProvider[],
|
||||
): void {
|
||||
memoryEmbeddingProviders.clear();
|
||||
for (const entry of entries) {
|
||||
registerMemoryEmbeddingProvider(entry.adapter, {
|
||||
ownerPluginId: entry.ownerPluginId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import { normalizePluginHttpPath } from "./http-path.js";
|
||||
import { findOverlappingPluginHttpRoute } from "./http-route-overlap.js";
|
||||
import { registerPluginInteractiveHandler } from "./interactive.js";
|
||||
import {
|
||||
getMemoryEmbeddingProvider,
|
||||
getRegisteredMemoryEmbeddingProvider,
|
||||
registerMemoryEmbeddingProvider,
|
||||
} from "./memory-embedding-providers.js";
|
||||
import {
|
||||
@@ -1106,17 +1106,29 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
||||
if (registrationMode !== "full") {
|
||||
return;
|
||||
}
|
||||
const existing = getMemoryEmbeddingProvider(adapter.id);
|
||||
if (existing) {
|
||||
if (record.kind !== "memory") {
|
||||
pushDiagnostic({
|
||||
level: "error",
|
||||
pluginId: record.id,
|
||||
source: record.source,
|
||||
message: `memory embedding provider already registered: ${adapter.id}`,
|
||||
message: "only memory plugins can register memory embedding providers",
|
||||
});
|
||||
return;
|
||||
}
|
||||
registerMemoryEmbeddingProvider(adapter);
|
||||
const existing = getRegisteredMemoryEmbeddingProvider(adapter.id);
|
||||
if (existing) {
|
||||
const ownerDetail = existing.ownerPluginId ? ` (owner: ${existing.ownerPluginId})` : "";
|
||||
pushDiagnostic({
|
||||
level: "error",
|
||||
pluginId: record.id,
|
||||
source: record.source,
|
||||
message: `memory embedding provider already registered: ${adapter.id}${ownerDetail}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
registerMemoryEmbeddingProvider(adapter, {
|
||||
ownerPluginId: record.id,
|
||||
});
|
||||
},
|
||||
resolvePath: (input: string) => resolveUserPath(input),
|
||||
on: (hookName, handler, opts) =>
|
||||
|
||||
Reference in New Issue
Block a user