restore extension-api backward compatibility with migration warning
This commit is contained in:
parent
e4d0fdcc15
commit
16e055c083
@ -166,6 +166,11 @@ my-plugin/
|
||||
Always import from specific `openclaw/plugin-sdk/\<subpath\>` paths. The old
|
||||
monolithic import is deprecated (see [SDK Migration](/plugins/sdk-migration)).
|
||||
|
||||
If older plugin code still imports `openclaw/extension-api`, treat that as a
|
||||
temporary compatibility bridge only. New code should use injected runtime
|
||||
helpers such as `api.runtime.agent.*` instead of importing host-side agent
|
||||
helpers directly.
|
||||
|
||||
```typescript
|
||||
// Correct: focused subpaths
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
||||
@ -174,6 +179,9 @@ my-plugin/
|
||||
|
||||
// Wrong: monolithic root (lint will reject this)
|
||||
import { ... } from "openclaw/plugin-sdk";
|
||||
|
||||
// Deprecated: legacy host bridge
|
||||
import { runEmbeddedPiAgent } from "openclaw/extension-api";
|
||||
```
|
||||
|
||||
<Accordion title="Common subpaths reference">
|
||||
@ -302,7 +310,7 @@ patterns is strongly recommended.
|
||||
|
||||
## Related
|
||||
|
||||
- [Plugin SDK Migration](/plugins/sdk-migration) — migrating from the deprecated compat import
|
||||
- [Plugin SDK Migration](/plugins/sdk-migration) — migrating from deprecated compat surfaces
|
||||
- [Plugin Architecture](/plugins/architecture) — internals and capability model
|
||||
- [Plugin Manifest](/plugins/manifest) — full manifest schema
|
||||
- [Plugin Agent Tools](/plugins/agent-tools) — adding agent tools in a plugin
|
||||
|
||||
@ -1,17 +1,24 @@
|
||||
---
|
||||
title: "Plugin SDK Migration"
|
||||
sidebarTitle: "SDK Migration"
|
||||
summary: "Migrate from the deprecated openclaw/plugin-sdk/compat import to focused subpath imports"
|
||||
summary: "Migrate from legacy compat surfaces to focused plugin-sdk subpaths and injected runtime helpers"
|
||||
read_when:
|
||||
- You see the OPENCLAW_PLUGIN_SDK_COMPAT_DEPRECATED warning
|
||||
- You are updating a plugin from the monolithic import to scoped subpaths
|
||||
- You see the OPENCLAW_EXTENSION_API_DEPRECATED warning
|
||||
- You are updating a plugin from the monolithic plugin-sdk import to scoped subpaths
|
||||
- You are updating a plugin away from openclaw/extension-api
|
||||
- You maintain an external OpenClaw plugin
|
||||
---
|
||||
|
||||
# Plugin SDK Migration
|
||||
|
||||
The `openclaw/plugin-sdk/compat` import is deprecated. All plugins should use
|
||||
**focused subpath imports** (`openclaw/plugin-sdk/\<subpath\>`) instead.
|
||||
OpenClaw is migrating from broad compatibility surfaces to narrower, documented
|
||||
contracts:
|
||||
|
||||
- `openclaw/plugin-sdk/compat` -> focused `openclaw/plugin-sdk/<subpath>` imports
|
||||
- `openclaw/extension-api` -> injected runtime helpers such as `api.runtime.agent.*`
|
||||
|
||||
This page explains what changed, why, and how to migrate.
|
||||
|
||||
<Info>
|
||||
The compat import still works at runtime. This is a deprecation warning, not
|
||||
@ -32,19 +39,21 @@ with a clear purpose.
|
||||
|
||||
<Steps>
|
||||
<Step title="Find deprecated imports">
|
||||
Search your plugin for imports from the compat path:
|
||||
Search your plugin for imports from either deprecated surface:
|
||||
|
||||
```bash
|
||||
grep -r "plugin-sdk/compat" my-plugin/
|
||||
grep -r "openclaw/extension-api" extensions/my-plugin/
|
||||
```
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Replace with focused subpaths">
|
||||
Each export maps to a specific subpath. Replace the import source:
|
||||
<Step title="Replace with focused subpaths or runtime injection">
|
||||
Each export from compat maps to a specific subpath. Replace the import
|
||||
source:
|
||||
|
||||
```typescript
|
||||
// Before (deprecated)
|
||||
// Before (compat entry)
|
||||
import {
|
||||
createChannelReplyPipeline,
|
||||
createPluginRuntimeStore,
|
||||
@ -57,14 +66,60 @@ with a clear purpose.
|
||||
import { resolveControlCommandGate } from "openclaw/plugin-sdk/command-auth";
|
||||
```
|
||||
|
||||
See the [subpath reference](#subpath-reference) below for the full mapping.
|
||||
If your plugin imports from `openclaw/extension-api`, you will now see:
|
||||
|
||||
```text
|
||||
[OPENCLAW_EXTENSION_API_DEPRECATED] Warning: openclaw/extension-api is deprecated.
|
||||
Migrate to api.runtime.agent.* or focused openclaw/plugin-sdk/<subpath> imports.
|
||||
```
|
||||
|
||||
That bridge also still works at runtime today. It exists to preserve older
|
||||
plugins while they migrate to the injected plugin runtime.
|
||||
|
||||
Move host-side helpers onto the injected plugin runtime instead of
|
||||
importing them directly:
|
||||
|
||||
```typescript
|
||||
// Before (deprecated extension-api bridge)
|
||||
import { runEmbeddedPiAgent } from "openclaw/extension-api";
|
||||
|
||||
const result = await runEmbeddedPiAgent({
|
||||
sessionId,
|
||||
sessionFile,
|
||||
workspaceDir,
|
||||
prompt,
|
||||
timeoutMs,
|
||||
});
|
||||
|
||||
// After (preferred injected runtime)
|
||||
const result = await api.runtime.agent.runEmbeddedPiAgent({
|
||||
sessionId,
|
||||
sessionFile,
|
||||
workspaceDir,
|
||||
prompt,
|
||||
timeoutMs,
|
||||
});
|
||||
```
|
||||
|
||||
The same pattern applies to the other legacy `extension-api` helpers:
|
||||
|
||||
- `resolveAgentDir` -> `api.runtime.agent.resolveAgentDir`
|
||||
- `resolveAgentWorkspaceDir` -> `api.runtime.agent.resolveAgentWorkspaceDir`
|
||||
- `resolveAgentIdentity` -> `api.runtime.agent.resolveAgentIdentity`
|
||||
- `resolveThinkingDefault` -> `api.runtime.agent.resolveThinkingDefault`
|
||||
- `resolveAgentTimeoutMs` -> `api.runtime.agent.resolveAgentTimeoutMs`
|
||||
- `ensureAgentWorkspace` -> `api.runtime.agent.ensureAgentWorkspace`
|
||||
- session store helpers -> `api.runtime.agent.session.*`
|
||||
|
||||
See the [subpath reference](#subpath-reference) below for the scoped import
|
||||
mapping.
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Build and test">
|
||||
```bash
|
||||
pnpm build
|
||||
pnpm test -- my-plugin/
|
||||
pnpm test -- extensions/my-plugin/
|
||||
```
|
||||
</Step>
|
||||
</Steps>
|
||||
@ -101,10 +156,10 @@ check the source at `src/plugin-sdk/` or ask in Discord.
|
||||
|
||||
## Removal timeline
|
||||
|
||||
| When | What happens |
|
||||
| ---------------------- | --------------------------------------------------------------- |
|
||||
| **Now** | Compat import emits a runtime deprecation warning |
|
||||
| **Next major release** | Compat import will be removed; plugins still using it will fail |
|
||||
| When | What happens |
|
||||
| --- | --- |
|
||||
| **Now** | Compat import and `openclaw/extension-api` emit runtime warnings |
|
||||
| **Next major release** | These legacy bridges may be removed; plugins still using them will fail |
|
||||
|
||||
All core plugins have already been migrated. External plugins should migrate
|
||||
before the next major release.
|
||||
@ -115,6 +170,7 @@ Set this environment variable while you work on migrating:
|
||||
|
||||
```bash
|
||||
OPENCLAW_SUPPRESS_PLUGIN_SDK_COMPAT_WARNING=1 openclaw gateway run
|
||||
OPENCLAW_SUPPRESS_EXTENSION_API_WARNING=1 openclaw gateway run
|
||||
```
|
||||
|
||||
This is a temporary escape hatch, not a permanent solution.
|
||||
|
||||
@ -513,6 +513,7 @@
|
||||
"types": "./dist/plugin-sdk/tool-send.d.ts",
|
||||
"default": "./dist/plugin-sdk/tool-send.js"
|
||||
},
|
||||
"./extension-api": "./dist/extensionAPI.js",
|
||||
"./cli-entry": "./openclaw.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
21
src/extensionAPI.test.ts
Normal file
21
src/extensionAPI.test.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import * as extensionApi from "openclaw/extension-api";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
describe("extension-api compat surface", () => {
|
||||
it("keeps legacy agent helpers importable", () => {
|
||||
expect(typeof extensionApi.runEmbeddedPiAgent).toBe("function");
|
||||
expect(typeof extensionApi.resolveAgentDir).toBe("function");
|
||||
expect(typeof extensionApi.resolveAgentWorkspaceDir).toBe("function");
|
||||
expect(typeof extensionApi.resolveAgentTimeoutMs).toBe("function");
|
||||
expect(typeof extensionApi.ensureAgentWorkspace).toBe("function");
|
||||
});
|
||||
|
||||
it("keeps legacy defaults and session helpers importable", () => {
|
||||
expect(typeof extensionApi.DEFAULT_MODEL).toBe("string");
|
||||
expect(typeof extensionApi.DEFAULT_PROVIDER).toBe("string");
|
||||
expect(typeof extensionApi.resolveStorePath).toBe("function");
|
||||
expect(typeof extensionApi.loadSessionStore).toBe("function");
|
||||
expect(typeof extensionApi.saveSessionStore).toBe("function");
|
||||
expect(typeof extensionApi.resolveSessionFilePath).toBe("function");
|
||||
});
|
||||
});
|
||||
32
src/extensionAPI.ts
Normal file
32
src/extensionAPI.ts
Normal file
@ -0,0 +1,32 @@
|
||||
// Legacy compat surface for plugins that still import openclaw/extension-api.
|
||||
// Keep this file intentionally narrow and forward-only.
|
||||
|
||||
const shouldWarnExtensionApiImport =
|
||||
process.env.VITEST !== "true" &&
|
||||
process.env.NODE_ENV !== "test" &&
|
||||
process.env.OPENCLAW_SUPPRESS_EXTENSION_API_WARNING !== "1";
|
||||
|
||||
if (shouldWarnExtensionApiImport) {
|
||||
process.emitWarning(
|
||||
"openclaw/extension-api is deprecated. Migrate to api.runtime.agent.* or focused openclaw/plugin-sdk/<subpath> imports. See https://docs.openclaw.ai/plugins/sdk-migration",
|
||||
{
|
||||
code: "OPENCLAW_EXTENSION_API_DEPRECATED",
|
||||
detail:
|
||||
"This compatibility bridge is temporary. Bundled plugins should use the injected plugin runtime instead of importing host-side agent helpers directly. Migration guide: https://docs.openclaw.ai/plugins/sdk-migration",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export { resolveAgentDir, resolveAgentWorkspaceDir } from "./agents/agent-scope.js";
|
||||
export { DEFAULT_MODEL, DEFAULT_PROVIDER } from "./agents/defaults.js";
|
||||
export { resolveAgentIdentity } from "./agents/identity.js";
|
||||
export { resolveThinkingDefault } from "./agents/model-selection.js";
|
||||
export { runEmbeddedPiAgent } from "./agents/pi-embedded.js";
|
||||
export { resolveAgentTimeoutMs } from "./agents/timeout.js";
|
||||
export { ensureAgentWorkspace } from "./agents/workspace.js";
|
||||
export {
|
||||
resolveStorePath,
|
||||
loadSessionStore,
|
||||
saveSessionStore,
|
||||
resolveSessionFilePath,
|
||||
} from "./config/sessions.js";
|
||||
@ -358,6 +358,23 @@ function createPluginSdkAliasFixture(params?: {
|
||||
return { root, srcFile, distFile };
|
||||
}
|
||||
|
||||
function createExtensionApiAliasFixture(params?: { srcBody?: string; distBody?: string }) {
|
||||
const root = makeTempDir();
|
||||
const srcFile = path.join(root, "src", "extensionAPI.ts");
|
||||
const distFile = path.join(root, "dist", "extensionAPI.js");
|
||||
mkdirSafe(path.dirname(srcFile));
|
||||
mkdirSafe(path.dirname(distFile));
|
||||
fs.writeFileSync(
|
||||
path.join(root, "package.json"),
|
||||
JSON.stringify({ name: "openclaw", type: "module" }, null, 2),
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(path.join(root, "openclaw.mjs"), "export {};\n", "utf-8");
|
||||
fs.writeFileSync(srcFile, params?.srcBody ?? "export {};\n", "utf-8");
|
||||
fs.writeFileSync(distFile, params?.distBody ?? "export {};\n", "utf-8");
|
||||
return { root, srcFile, distFile };
|
||||
}
|
||||
|
||||
function createPluginRuntimeAliasFixture(params?: { srcBody?: string; distBody?: string }) {
|
||||
const root = makeTempDir();
|
||||
const srcFile = path.join(root, "src", "plugins", "runtime", "index.ts");
|
||||
@ -3354,6 +3371,36 @@ module.exports = {
|
||||
expect(resolved).toBe(expected === "dist" ? fixture.distFile : fixture.srcFile);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "prefers dist extension-api alias when loader runs from dist",
|
||||
modulePath: (root: string) => path.join(root, "dist", "plugins", "loader.js"),
|
||||
expected: "dist" as const,
|
||||
},
|
||||
{
|
||||
name: "prefers src extension-api alias when loader runs from src in non-production",
|
||||
modulePath: (root: string) => path.join(root, "src", "plugins", "loader.ts"),
|
||||
env: { NODE_ENV: undefined },
|
||||
expected: "src" as const,
|
||||
},
|
||||
{
|
||||
name: "resolves extension-api alias from package root when loader runs from transpiler cache path",
|
||||
modulePath: () => "/tmp/tsx-cache/openclaw-loader.js",
|
||||
argv1: (root: string) => path.join(root, "openclaw.mjs"),
|
||||
env: { NODE_ENV: undefined },
|
||||
expected: "src" as const,
|
||||
},
|
||||
])("$name", ({ modulePath, argv1, env, expected }) => {
|
||||
const fixture = createExtensionApiAliasFixture();
|
||||
const resolved = withEnv(env ?? {}, () =>
|
||||
__testing.resolveExtensionApiAlias({
|
||||
modulePath: modulePath(fixture.root),
|
||||
argv1: argv1?.(fixture.root),
|
||||
}),
|
||||
);
|
||||
expect(resolved).toBe(expected === "dist" ? fixture.distFile : fixture.srcFile);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "prefers dist candidates first for production src runtime",
|
||||
|
||||
@ -130,12 +130,42 @@ const resolvePluginSdkAlias = (params: LoaderModuleResolveParams = {}): string |
|
||||
|
||||
function buildPluginLoaderAliasMap(modulePath: string): Record<string, string> {
|
||||
const pluginSdkAlias = resolvePluginSdkAlias({ modulePath });
|
||||
const extensionApiAlias = resolveExtensionApiAlias({ modulePath });
|
||||
return {
|
||||
...(extensionApiAlias ? { "openclaw/extension-api": extensionApiAlias } : {}),
|
||||
...(pluginSdkAlias ? { "openclaw/plugin-sdk": pluginSdkAlias } : {}),
|
||||
...resolvePluginSdkScopedAliasMap({ modulePath }),
|
||||
};
|
||||
}
|
||||
|
||||
const resolveExtensionApiAlias = (params: LoaderModuleResolveParams = {}): string | null => {
|
||||
try {
|
||||
const modulePath = resolveLoaderModulePath(params);
|
||||
const packageRoot = resolveLoaderPackageRoot({ ...params, modulePath });
|
||||
if (!packageRoot) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const orderedKinds = resolvePluginSdkAliasCandidateOrder({
|
||||
modulePath,
|
||||
isProduction: process.env.NODE_ENV === "production",
|
||||
});
|
||||
const candidateMap = {
|
||||
src: path.join(packageRoot, "src", "extensionAPI.ts"),
|
||||
dist: path.join(packageRoot, "dist", "extensionAPI.js"),
|
||||
} as const;
|
||||
for (const kind of orderedKinds) {
|
||||
const candidate = candidateMap[kind];
|
||||
if (fs.existsSync(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
function resolvePluginRuntimeModulePath(params: LoaderModuleResolveParams = {}): string | null {
|
||||
try {
|
||||
const modulePath = resolveLoaderModulePath(params);
|
||||
@ -170,6 +200,7 @@ export const __testing = {
|
||||
buildPluginLoaderAliasMap,
|
||||
listPluginSdkAliasCandidates,
|
||||
listPluginSdkExportedSubpaths,
|
||||
resolveExtensionApiAlias,
|
||||
resolvePluginSdkScopedAliasMap,
|
||||
resolvePluginSdkAliasCandidateOrder,
|
||||
resolvePluginSdkAliasFile,
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
"target": "es2023",
|
||||
"useDefineForClassFields": false,
|
||||
"paths": {
|
||||
"openclaw/extension-api": ["./src/extensionAPI.ts"],
|
||||
"openclaw/plugin-sdk": ["./src/plugin-sdk/index.ts"],
|
||||
"openclaw/plugin-sdk/*": ["./src/plugin-sdk/*.ts"],
|
||||
"openclaw/plugin-sdk/account-id": ["./src/plugin-sdk/account-id.ts"]
|
||||
|
||||
@ -169,6 +169,7 @@ function buildCoreDistEntries(): Record<string, string> {
|
||||
entry: "src/entry.ts",
|
||||
// Ensure this module is bundled as an entry so legacy CLI shims can resolve its exports.
|
||||
"cli/daemon-cli": "src/cli/daemon-cli.ts",
|
||||
extensionAPI: "src/extensionAPI.ts",
|
||||
"infra/warning-filter": "src/infra/warning-filter.ts",
|
||||
"telegram/audit": "extensions/telegram/src/audit.ts",
|
||||
"telegram/token": "extensions/telegram/src/token.ts",
|
||||
|
||||
@ -13,6 +13,10 @@ export default defineConfig({
|
||||
resolve: {
|
||||
// Keep this ordered: the base `openclaw/plugin-sdk` alias is a prefix match.
|
||||
alias: [
|
||||
{
|
||||
find: "openclaw/extension-api",
|
||||
replacement: path.join(repoRoot, "src", "extensionAPI.ts"),
|
||||
},
|
||||
...pluginSdkSubpaths.map((subpath) => ({
|
||||
find: `openclaw/plugin-sdk/${subpath}`,
|
||||
replacement: path.join(repoRoot, "src", "plugin-sdk", `${subpath}.ts`),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user