diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dd44bb0b167..ef3d7693bbda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Docs: https://docs.openclaw.ai ### Changes - Memory: add a core OpenAI-compatible embedding provider for local and hosted OpenAI-style endpoints, with config, doctor, and docs support. (#85269) Thanks @dutifulbob. +- Plugin SDK: mark memory-specific embedding provider registration as deprecated compatibility and surface non-bundled usage in plugin compatibility diagnostics. (#85072) Thanks @mbelinky. ### Fixes diff --git a/docs/plugins/adding-capabilities.md b/docs/plugins/adding-capabilities.md index d7b7df5be55d..c7002598ac45 100644 --- a/docs/plugins/adding-capabilities.md +++ b/docs/plugins/adding-capabilities.md @@ -122,9 +122,9 @@ future feature plugins can consume embeddings without depending on the memory engine. Memory search can consume generic `embeddingProviders`. The older -`memoryEmbeddingProviders` contract remains for compatibility while existing -memory-specific providers migrate, but new reusable embedding providers should -use `embeddingProviders`. +`memoryEmbeddingProviders` contract is deprecated compatibility while existing +memory-specific providers migrate; new reusable embedding providers should use +`embeddingProviders`. ## Review checklist diff --git a/docs/plugins/compatibility.md b/docs/plugins/compatibility.md index b637e2b04d17..171251280ca5 100644 --- a/docs/plugins/compatibility.md +++ b/docs/plugins/compatibility.md @@ -130,6 +130,9 @@ Current compatibility records include: `api.runtime.config.loadConfig()` / `api.runtime.config.writeConfigFile(...)` - legacy memory-plugin split registration while memory plugins move to `registerMemoryCapability` +- legacy memory-specific embedding provider registration while embedding + providers move to `api.registerEmbeddingProvider(...)` and + `contracts.embeddingProviders` - legacy channel SDK helpers for native message schemas, mention gating, inbound envelope formatting, and approval capability nesting - legacy channel route key and comparable-target helper aliases while plugins diff --git a/docs/plugins/manifest.md b/docs/plugins/manifest.md index abdc8c54b874..26e73fc5447e 100644 --- a/docs/plugins/manifest.md +++ b/docs/plugins/manifest.md @@ -647,7 +647,7 @@ Each list is optional: | `speechProviders` | `string[]` | Speech provider ids this plugin owns. | | `realtimeTranscriptionProviders` | `string[]` | Realtime-transcription provider ids this plugin owns. | | `realtimeVoiceProviders` | `string[]` | Realtime-voice provider ids this plugin owns. | -| `memoryEmbeddingProviders` | `string[]` | Legacy memory embedding provider ids this plugin owns. | +| `memoryEmbeddingProviders` | `string[]` | Deprecated memory-specific embedding provider ids this plugin owns. | | `mediaUnderstandingProviders` | `string[]` | Media-understanding provider ids this plugin owns. | | `transcriptSourceProviders` | `string[]` | Transcript source provider ids this plugin owns. | | `imageGenerationProviders` | `string[]` | Image-generation provider ids this plugin owns. | @@ -677,9 +677,9 @@ will be removed after the migration window. General embedding providers should declare `contracts.embeddingProviders` for each adapter registered with `api.registerEmbeddingProvider(...)`. Use the general contract for reusable vector generation, including providers consumed by -memory search. `contracts.memoryEmbeddingProviders` is the older -memory-specific compatibility contract and remains while existing providers -migrate to the generic embedding provider seam. +memory search. `contracts.memoryEmbeddingProviders` is deprecated +memory-specific compatibility and remains only while existing providers migrate +to the generic embedding provider seam. `contracts.gatewayMethodDispatch` currently accepts `"authenticated-request"`. It is an API hygiene gate for native plugin HTTP diff --git a/docs/plugins/sdk-migration.md b/docs/plugins/sdk-migration.md index 8ed28dbc2aaf..2745ae09b5e8 100644 --- a/docs/plugins/sdk-migration.md +++ b/docs/plugins/sdk-migration.md @@ -868,9 +868,23 @@ canonical replacement. **New**: one call on the memory-state API - `registerMemoryCapability(pluginId, { promptBuilder, flushPlanResolver, runtime })`. - Same slots, single registration call. Additive memory helpers - (`registerMemoryPromptSupplement`, `registerMemoryCorpusSupplement`, - `registerMemoryEmbeddingProvider`) are not affected. + Same slots, single registration call. Additive prompt and corpus helpers + (`registerMemoryPromptSupplement`, `registerMemoryCorpusSupplement`) are + not affected. + + + + + **Old**: `api.registerMemoryEmbeddingProvider(...)` plus + `contracts.memoryEmbeddingProviders`. + + **New**: `api.registerEmbeddingProvider(...)` plus + `contracts.embeddingProviders`. + + The generic embedding provider contract is reusable outside memory and is + the supported path for new providers. The memory-specific registration API + remains wired as deprecated compatibility while existing providers migrate. + Plugin inspection reports non-bundled usage as compatibility debt. diff --git a/docs/plugins/sdk-overview.md b/docs/plugins/sdk-overview.md index 25a1f74adfac..d6879fd41b6e 100644 --- a/docs/plugins/sdk-overview.md +++ b/docs/plugins/sdk-overview.md @@ -111,7 +111,7 @@ also be listed in `contracts.embeddingProviders` in the plugin manifest. This is the generic embedding surface for reusable vector generation. Memory search can consume this generic provider surface. The older `api.registerMemoryEmbeddingProvider(...)` and -`contracts.memoryEmbeddingProviders` seam remains as compatibility while +`contracts.memoryEmbeddingProviders` seam is deprecated compatibility while existing memory-specific providers migrate. ### Tools and commands @@ -378,7 +378,7 @@ For an end-to-end authoring guide, see | `api.registerMemoryFlushPlan(resolver)` | Memory flush plan resolver | | `api.registerMemoryRuntime(runtime)` | Memory runtime adapter | -### Memory embedding adapters +### Deprecated memory embedding adapters | Method | What it registers | | ---------------------------------------------- | ---------------------------------------------- | @@ -394,12 +394,12 @@ For an end-to-end authoring guide, see - `MemoryFlushPlan.model` can pin the flush turn to an exact `provider/model` reference, such as `ollama/qwen3:8b`, without inheriting the active fallback chain. -- `registerMemoryEmbeddingProvider` lets the active memory plugin register one - or more embedding adapter ids (for example `openai`, `gemini`, or a custom - plugin-defined id). -- User config such as `agents.defaults.memorySearch.provider` and - `agents.defaults.memorySearch.fallback` resolves against those registered - adapter ids. +- `registerMemoryEmbeddingProvider` is deprecated. New embedding providers + should use `api.registerEmbeddingProvider(...)` and + `contracts.embeddingProviders`. +- Existing memory-specific providers continue to work during the migration + window, but plugin inspection reports this as compatibility debt for + non-bundled plugins. ### Events and lifecycle diff --git a/docs/plugins/sdk-provider-plugins.md b/docs/plugins/sdk-provider-plugins.md index 355ce68e7ae5..108e6793a448 100644 --- a/docs/plugins/sdk-provider-plugins.md +++ b/docs/plugins/sdk-provider-plugins.md @@ -685,9 +685,9 @@ API key auth, and dynamic model resolution. ``` Declare the same id in `contracts.embeddingProviders`. This is the - general embedding contract for reusable vector generation. Use - `registerMemoryEmbeddingProvider(...)` only for memory-engine-specific - adapters. + general embedding contract for reusable vector generation, including + memory search. `registerMemoryEmbeddingProvider(...)` is deprecated + compatibility for existing memory-specific adapters. Video capabilities use a **mode-aware** shape: `generate`, diff --git a/docs/plugins/sdk-subpaths.md b/docs/plugins/sdk-subpaths.md index 1c00f7dbe951..f85b6a200f23 100644 --- a/docs/plugins/sdk-subpaths.md +++ b/docs/plugins/sdk-subpaths.md @@ -342,7 +342,7 @@ and pairing-path families. | `plugin-sdk/memory-core` | Bundled memory-core helper surface for manager/config/file/CLI helpers | | `plugin-sdk/memory-core-engine-runtime` | Memory index/search runtime facade | | `plugin-sdk/memory-core-host-engine-foundation` | Memory host foundation engine exports | - | `plugin-sdk/memory-core-host-engine-embeddings` | Memory host embedding contracts, registry access, local provider, and generic batch/remote helpers | + | `plugin-sdk/memory-core-host-engine-embeddings` | Memory host embedding contracts, registry access, local provider, and generic batch/remote helpers. `registerMemoryEmbeddingProvider` on this surface is deprecated; use the generic embedding provider API for new providers. | | `plugin-sdk/memory-core-host-engine-qmd` | Memory host QMD engine exports | | `plugin-sdk/memory-core-host-engine-storage` | Memory host storage engine exports | | `plugin-sdk/memory-core-host-multimodal` | Memory host multimodal helpers | diff --git a/src/plugin-sdk/memory-core-host-engine-embeddings.ts b/src/plugin-sdk/memory-core-host-engine-embeddings.ts index e2807646d081..8af451b510e9 100644 --- a/src/plugin-sdk/memory-core-host-engine-embeddings.ts +++ b/src/plugin-sdk/memory-core-host-engine-embeddings.ts @@ -57,10 +57,13 @@ export { listRegisteredMemoryEmbeddingProviderAdapters, listRegisteredMemoryEmbeddingProviders, } from "../plugins/memory-embedding-provider-runtime.js"; -export { - clearMemoryEmbeddingProviders, - registerMemoryEmbeddingProvider, -} from "../plugins/memory-embedding-providers.js"; +export { clearMemoryEmbeddingProviders } from "../plugins/memory-embedding-providers.js"; +/** + * @deprecated New embedding providers should use `api.registerEmbeddingProvider(...)` + * and `contracts.embeddingProviders`. This memory-specific registrar remains + * available only for compatibility while existing providers migrate. + */ +export { registerMemoryEmbeddingProvider } from "../plugins/memory-embedding-providers.js"; export type { MemoryEmbeddingBatchChunk, MemoryEmbeddingBatchOptions, diff --git a/src/plugins/compat/registry.test.ts b/src/plugins/compat/registry.test.ts index 50c237a78916..829910dab5d9 100644 --- a/src/plugins/compat/registry.test.ts +++ b/src/plugins/compat/registry.test.ts @@ -157,6 +157,11 @@ const knownDeprecatedSurfaceMarkers = [ file: "src/plugins/hook-types.ts", marker: "@deprecated Use gateway_stop", }, + { + code: "deprecated-memory-embedding-provider-api", + file: "src/plugins/types.ts", + marker: "registerMemoryEmbeddingProvider", + }, { code: "channel-route-key-aliases", file: "src/plugin-sdk/channel-route.ts", diff --git a/src/plugins/compat/registry.ts b/src/plugins/compat/registry.ts index bb233ca4356d..807f09980728 100644 --- a/src/plugins/compat/registry.ts +++ b/src/plugins/compat/registry.ts @@ -50,6 +50,31 @@ export const PLUGIN_COMPAT_RECORDS = [ diagnostics: ["plugin compatibility notice"], tests: ["src/plugins/status.test.ts", "src/plugins/contracts/shape.contract.test.ts"], }, + { + code: "deprecated-memory-embedding-provider-api", + status: "deprecated", + owner: "sdk", + introduced: "2026-05-21", + deprecated: "2026-05-21", + warningStarts: "2026-05-21", + removeAfter: "2026-08-21", + replacement: "`api.registerEmbeddingProvider(...)` and `contracts.embeddingProviders`", + docsPath: "/plugins/sdk-migration#memory-embedding-provider-api", + surfaces: [ + "api.registerMemoryEmbeddingProvider(...)", + "contracts.memoryEmbeddingProviders", + "openclaw/plugin-sdk/memory-core-host-engine-embeddings registerMemoryEmbeddingProvider", + "plugins inspect compatibility notices", + ], + diagnostics: ["plugin compatibility notice", "plugin SDK package guardrail"], + tests: [ + "src/plugins/status.test.ts", + "src/plugins/compat/registry.test.ts", + "src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts", + ], + releaseNote: + "Memory-specific embedding provider registration remains wired as a deprecated compatibility path while providers migrate to the generic embedding provider contract.", + }, { code: "legacy-root-sdk-import", status: "deprecated", diff --git a/src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts b/src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts index f8863dcea9ef..2c4ae6831a85 100644 --- a/src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts +++ b/src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts @@ -54,6 +54,30 @@ const DEPRECATED_TEST_BARREL_ALLOWED_REFERENCE_FILES = new Set([ "src/plugins/contracts/plugin-entry-guardrails.test.ts", "src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts", ]); +const LEGACY_MEMORY_EMBEDDING_PROVIDER_API_FILES = new Set([ + "extensions/amazon-bedrock/register.sync.runtime.ts", + "extensions/deepinfra/index.ts", + "extensions/github-copilot/index.ts", + "extensions/google/index.ts", + "extensions/lmstudio/index.ts", + "extensions/memory-core/src/memory/provider-adapters.ts", + "extensions/mistral/index.ts", + "extensions/ollama/index.ts", + "extensions/openai/index.ts", + "extensions/voyage/index.ts", +]); +const LEGACY_MEMORY_EMBEDDING_PROVIDER_MANIFEST_FILES = new Set([ + "extensions/amazon-bedrock/openclaw.plugin.json", + "extensions/deepinfra/openclaw.plugin.json", + "extensions/github-copilot/openclaw.plugin.json", + "extensions/google/openclaw.plugin.json", + "extensions/lmstudio/openclaw.plugin.json", + "extensions/memory-core/openclaw.plugin.json", + "extensions/mistral/openclaw.plugin.json", + "extensions/ollama/openclaw.plugin.json", + "extensions/openai/openclaw.plugin.json", + "extensions/voyage/openclaw.plugin.json", +]); const MATRIX_RUNTIME_DEPS = [ "@matrix-org/matrix-sdk-crypto-wasm", "@matrix-org/matrix-sdk-crypto-nodejs", @@ -396,6 +420,43 @@ function collectDeprecatedExtensionSdkImports(): Array<{ file: string; specifier return leaks; } +function collectNewDeprecatedMemoryEmbeddingProviderApiFiles(): string[] { + const files: string[] = []; + for (const file of collectExtensionFiles(resolve(REPO_ROOT, "extensions"))) { + const repoRelativePath = toRepoRelativePath(file); + if (isExtensionTestOrSupportPath(repoRelativePath)) { + continue; + } + const source = fs.readFileSync(file, "utf8"); + if ( + /\b(?:[A-Za-z_$][\w$]*\.)?registerMemoryEmbeddingProvider\s*\(/u.test(source) && + !LEGACY_MEMORY_EMBEDDING_PROVIDER_API_FILES.has(repoRelativePath) + ) { + files.push(repoRelativePath); + } + } + return files.toSorted(); +} + +function collectNewDeprecatedMemoryEmbeddingProviderManifestFiles(): string[] { + const files: string[] = []; + const manifestFiles = + listGitTrackedFiles({ + repoRoot: REPO_ROOT, + pathspecs: "extensions/**/openclaw.plugin.json", + }) ?? []; + for (const repoRelativePath of manifestFiles) { + const source = fs.readFileSync(resolve(REPO_ROOT, repoRelativePath), "utf8"); + if ( + /"memoryEmbeddingProviders"\s*:/u.test(source) && + !LEGACY_MEMORY_EMBEDDING_PROVIDER_MANIFEST_FILES.has(repoRelativePath) + ) { + files.push(repoRelativePath); + } + } + return files.toSorted(); +} + function collectCodeFiles(dir: string): string[] { const trackedFiles = listTrackedCodeFiles(dir); if (trackedFiles) { @@ -867,6 +928,16 @@ describe("plugin-sdk package contract guardrails", () => { expect(collectDeprecatedExtensionSdkImports()).toStrictEqual([]); }); + it("keeps new bundled plugins off deprecated memory embedding provider registration", () => { + expect({ + apiFiles: collectNewDeprecatedMemoryEmbeddingProviderApiFiles(), + manifestFiles: collectNewDeprecatedMemoryEmbeddingProviderManifestFiles(), + }).toStrictEqual({ + apiFiles: [], + manifestFiles: [], + }); + }); + it("keeps real tests off deprecated plugin-sdk testing barrels", () => { expect(collectDeprecatedTestBarrelImports()).toStrictEqual([]); }); diff --git a/src/plugins/status.test-helpers.ts b/src/plugins/status.test-helpers.ts index 5116e33062a7..bdf8232b7471 100644 --- a/src/plugins/status.test-helpers.ts +++ b/src/plugins/status.test-helpers.ts @@ -7,6 +7,8 @@ export const LEGACY_BEFORE_AGENT_START_MESSAGE = "still uses legacy before_agent_start; keep regression coverage on this plugin, and prefer before_model_resolve/before_prompt_build for new work."; export const HOOK_ONLY_MESSAGE = "is hook-only. This remains a supported compatibility path, but it has not migrated to explicit capability registration yet."; +export const DEPRECATED_MEMORY_EMBEDDING_PROVIDER_API_MESSAGE = + "uses deprecated memory-specific embedding provider API; use api.registerEmbeddingProvider and contracts.embeddingProviders for new embedding providers."; export function createCompatibilityNotice( params: Pick, @@ -28,6 +30,14 @@ export function createCompatibilityNotice( severity: "info", message: HOOK_ONLY_MESSAGE, }; + case "deprecated-memory-embedding-provider-api": + return { + pluginId: params.pluginId, + code: params.code, + compatCode: "deprecated-memory-embedding-provider-api", + severity: "warn", + message: DEPRECATED_MEMORY_EMBEDDING_PROVIDER_API_MESSAGE, + }; } const unsupportedCode: never = params.code; void unsupportedCode; diff --git a/src/plugins/status.test.ts b/src/plugins/status.test.ts index 55f1973f6731..b0323c63c6e0 100644 --- a/src/plugins/status.test.ts +++ b/src/plugins/status.test.ts @@ -1,10 +1,12 @@ import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import type { PluginMemoryEmbeddingProviderRegistration } from "./registry-types.js"; import { createCompatibilityNotice, createCustomHook, createPluginLoadResult, createPluginRecord, createTypedHook, + DEPRECATED_MEMORY_EMBEDDING_PROVIDER_API_MESSAGE, HOOK_ONLY_MESSAGE, LEGACY_BEFORE_AGENT_START_MESSAGE, } from "./status.test-helpers.js"; @@ -838,6 +840,74 @@ describe("plugin status reports", () => { }); }); + it("warns external plugins off deprecated memory embedding provider registration", () => { + setSinglePluginLoadResult( + createPluginRecord({ + id: "legacy-memory-provider", + name: "Legacy Memory Provider", + memoryEmbeddingProviderIds: ["legacy-memory-provider"], + contracts: { memoryEmbeddingProviders: ["legacy-memory-provider"] }, + }), + ); + + expectCompatibilityOutput({ + notices: [ + createCompatibilityNotice({ + pluginId: "legacy-memory-provider", + code: "deprecated-memory-embedding-provider-api", + }), + ], + warnings: [`legacy-memory-provider ${DEPRECATED_MEMORY_EMBEDDING_PROVIDER_API_MESSAGE}`], + }); + }); + + it("warns when external plugins register memory embedding providers at runtime only", () => { + const runtimeProviderRegistration: PluginMemoryEmbeddingProviderRegistration = { + pluginId: "runtime-only-legacy-memory-provider", + pluginName: "Runtime Only Legacy Memory Provider", + provider: { + id: "runtime-only-legacy-memory-provider", + create: async () => ({ provider: null }), + }, + source: "/tmp/runtime-only-legacy-memory-provider/index.ts", + }; + setPluginLoadResult({ + plugins: [ + createPluginRecord({ + id: "runtime-only-legacy-memory-provider", + name: "Runtime Only Legacy Memory Provider", + }), + ], + memoryEmbeddingProviders: [runtimeProviderRegistration], + }); + + expectCompatibilityOutput({ + notices: [ + createCompatibilityNotice({ + pluginId: "runtime-only-legacy-memory-provider", + code: "deprecated-memory-embedding-provider-api", + }), + ], + warnings: [ + `runtime-only-legacy-memory-provider ${DEPRECATED_MEMORY_EMBEDDING_PROVIDER_API_MESSAGE}`, + ], + }); + }); + + it("does not surface bundled memory embedding migration debt as user warnings", () => { + setSinglePluginLoadResult( + createPluginRecord({ + id: "bundled-memory-provider", + name: "Bundled Memory Provider", + origin: "bundled", + memoryEmbeddingProviderIds: ["bundled-memory-provider"], + contracts: { memoryEmbeddingProviders: ["bundled-memory-provider"] }, + }), + ); + + expectNoCompatibilityWarnings(); + }); + it("builds structured compatibility notices with deterministic ordering", () => { setPluginLoadResult({ plugins: [ diff --git a/src/plugins/status.ts b/src/plugins/status.ts index 9de78eabc7af..0118363ed6c3 100644 --- a/src/plugins/status.ts +++ b/src/plugins/status.ts @@ -53,7 +53,7 @@ export type { PluginCapabilityKind, PluginInspectShape } from "./inspect-shape.j export type PluginCompatibilityNotice = { pluginId: string; - code: "legacy-before-agent-start" | "hook-only"; + code: "legacy-before-agent-start" | "hook-only" | "deprecated-memory-embedding-provider-api"; compatCode: PluginCompatCode; severity: "warn" | "info"; message: string; @@ -113,7 +113,9 @@ export type PluginInspectReport = { }; function buildCompatibilityNoticesForInspect( - inspect: Pick, + inspect: Pick & { + hasRuntimeMemoryEmbeddingProviderRegistration: boolean; + }, ): PluginCompatibilityNotice[] { const warnings: PluginCompatibilityNotice[] = []; if (inspect.usesLegacyBeforeAgentStart) { @@ -136,6 +138,20 @@ function buildCompatibilityNoticesForInspect( "is hook-only. This remains a supported compatibility path, but it has not migrated to explicit capability registration yet.", }); } + const usesMemoryEmbeddingProviderApi = + inspect.plugin.memoryEmbeddingProviderIds.length > 0 || + (inspect.plugin.contracts?.memoryEmbeddingProviders?.length ?? 0) > 0 || + inspect.hasRuntimeMemoryEmbeddingProviderRegistration; + if (usesMemoryEmbeddingProviderApi && inspect.plugin.origin !== "bundled") { + warnings.push({ + pluginId: inspect.plugin.id, + code: "deprecated-memory-embedding-provider-api", + compatCode: "deprecated-memory-embedding-provider-api", + severity: "warn", + message: + "uses deprecated memory-specific embedding provider API; use api.registerEmbeddingProvider and contracts.embeddingProviders for new embedding providers.", + }); + } return warnings; } @@ -495,10 +511,14 @@ export function buildPluginInspectReport(params: { } const usesLegacyBeforeAgentStart = shapeSummary.usesLegacyBeforeAgentStart; + const hasRuntimeMemoryEmbeddingProviderRegistration = report.memoryEmbeddingProviders.some( + (entry) => entry.pluginId === plugin.id, + ); const compatibility = buildCompatibilityNoticesForInspect({ plugin, shape, usesLegacyBeforeAgentStart, + hasRuntimeMemoryEmbeddingProviderRegistration, }); return { workspaceDir: report.workspaceDir,