mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-22 22:52:03 +00:00
refactor(plugins): harden package boundary sdk prep
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key";
|
||||
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
|
||||
import { applyHuggingfaceConfig, HUGGINGFACE_DEFAULT_MODEL_REF } from "./onboard.js";
|
||||
import { buildHuggingfaceProvider } from "./provider-catalog.js";
|
||||
|
||||
@@ -11,60 +10,51 @@ type HuggingFacePluginConfig = {
|
||||
};
|
||||
};
|
||||
|
||||
export default definePluginEntry({
|
||||
export default defineSingleProviderPluginEntry({
|
||||
id: PROVIDER_ID,
|
||||
name: "Hugging Face Provider",
|
||||
description: "Bundled Hugging Face provider plugin",
|
||||
register(api) {
|
||||
const pluginConfig = (api.pluginConfig ?? {}) as HuggingFacePluginConfig;
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: "Hugging Face",
|
||||
docsPath: "/providers/huggingface",
|
||||
envVars: ["HUGGINGFACE_HUB_TOKEN", "HF_TOKEN"],
|
||||
auth: [
|
||||
createProviderApiKeyAuthMethod({
|
||||
providerId: PROVIDER_ID,
|
||||
methodId: "api-key",
|
||||
label: "Hugging Face API key",
|
||||
hint: "Inference API (HF token)",
|
||||
optionKey: "huggingfaceApiKey",
|
||||
flagName: "--huggingface-api-key",
|
||||
envVar: "HUGGINGFACE_HUB_TOKEN",
|
||||
promptMessage: "Enter Hugging Face API key",
|
||||
defaultModel: HUGGINGFACE_DEFAULT_MODEL_REF,
|
||||
expectedProviders: ["huggingface"],
|
||||
applyConfig: (cfg) => applyHuggingfaceConfig(cfg),
|
||||
wizard: {
|
||||
choiceId: "huggingface-api-key",
|
||||
choiceLabel: "Hugging Face API key",
|
||||
choiceHint: "Inference API (HF token)",
|
||||
groupId: "huggingface",
|
||||
groupLabel: "Hugging Face",
|
||||
groupHint: "Inference API (HF token)",
|
||||
},
|
||||
}),
|
||||
],
|
||||
catalog: {
|
||||
order: "simple",
|
||||
run: async (ctx) => {
|
||||
const discoveryEnabled =
|
||||
pluginConfig.discovery?.enabled ?? ctx.config?.models?.huggingfaceDiscovery?.enabled;
|
||||
if (discoveryEnabled === false) {
|
||||
return null;
|
||||
}
|
||||
const { apiKey, discoveryApiKey } = ctx.resolveProviderApiKey(PROVIDER_ID);
|
||||
if (!apiKey) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
provider: {
|
||||
...(await buildHuggingfaceProvider(discoveryApiKey)),
|
||||
apiKey,
|
||||
},
|
||||
};
|
||||
},
|
||||
provider: {
|
||||
label: "Hugging Face",
|
||||
docsPath: "/providers/huggingface",
|
||||
envVars: ["HUGGINGFACE_HUB_TOKEN", "HF_TOKEN"],
|
||||
auth: [
|
||||
{
|
||||
methodId: "api-key",
|
||||
label: "Hugging Face API key",
|
||||
hint: "Inference API (HF token)",
|
||||
optionKey: "huggingfaceApiKey",
|
||||
flagName: "--huggingface-api-key",
|
||||
envVar: "HUGGINGFACE_HUB_TOKEN",
|
||||
promptMessage: "Enter Hugging Face API key",
|
||||
defaultModel: HUGGINGFACE_DEFAULT_MODEL_REF,
|
||||
applyConfig: (cfg) => applyHuggingfaceConfig(cfg),
|
||||
},
|
||||
});
|
||||
],
|
||||
catalog: {
|
||||
order: "simple",
|
||||
run: async (ctx) => {
|
||||
const pluginEntry = ctx.config?.plugins?.entries?.[PROVIDER_ID];
|
||||
const pluginConfig =
|
||||
pluginEntry && typeof pluginEntry === "object" && pluginEntry.config
|
||||
? (pluginEntry.config as HuggingFacePluginConfig)
|
||||
: undefined;
|
||||
const discoveryEnabled =
|
||||
pluginConfig?.discovery?.enabled ?? ctx.config?.models?.huggingfaceDiscovery?.enabled;
|
||||
if (discoveryEnabled === false) {
|
||||
return null;
|
||||
}
|
||||
const { apiKey, discoveryApiKey } = ctx.resolveProviderApiKey(PROVIDER_ID);
|
||||
if (!apiKey) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
provider: {
|
||||
...(await buildHuggingfaceProvider(discoveryApiKey)),
|
||||
apiKey,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-types";
|
||||
|
||||
export const HUGGINGFACE_BASE_URL = "https://router.huggingface.co/v1";
|
||||
export const HUGGINGFACE_POLICY_SUFFIXES = ["cheapest", "fastest"] as const;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-types";
|
||||
import {
|
||||
buildHuggingfaceModelDefinition,
|
||||
discoverHuggingfaceModels,
|
||||
|
||||
@@ -5,22 +5,6 @@
|
||||
"openclaw/extension-api": ["../src/extensionAPI.ts"],
|
||||
"openclaw/plugin-sdk": ["../dist/plugin-sdk/index.d.ts"],
|
||||
"openclaw/plugin-sdk/*": ["../dist/plugin-sdk/*.d.ts"],
|
||||
"openclaw/plugin-sdk/account-id": ["../dist/plugin-sdk/account-id.d.ts"],
|
||||
"openclaw/plugin-sdk/channel-entry-contract": [
|
||||
"../packages/plugin-sdk/dist/src/plugin-sdk/channel-entry-contract.d.ts"
|
||||
],
|
||||
"openclaw/plugin-sdk/browser-maintenance": [
|
||||
"../packages/plugin-sdk/dist/extensions/browser/browser-maintenance.d.ts"
|
||||
],
|
||||
"openclaw/plugin-sdk/provider-catalog-shared": [
|
||||
"../packages/plugin-sdk/dist/src/plugin-sdk/provider-catalog-shared.d.ts"
|
||||
],
|
||||
"openclaw/plugin-sdk/provider-entry": [
|
||||
"../packages/plugin-sdk/dist/src/plugin-sdk/provider-entry.d.ts"
|
||||
],
|
||||
"openclaw/plugin-sdk/secret-ref-runtime": [
|
||||
"../dist/plugin-sdk/src/plugin-sdk/secret-ref-runtime.d.ts"
|
||||
],
|
||||
"@openclaw/*.js": ["../packages/plugin-sdk/dist/extensions/*.d.ts", "../extensions/*"],
|
||||
"@openclaw/*": ["../packages/plugin-sdk/dist/extensions/*", "../extensions/*"],
|
||||
"@openclaw/plugin-sdk/*": ["../dist/plugin-sdk/*.d.ts"]
|
||||
|
||||
@@ -6,22 +6,6 @@
|
||||
"openclaw/extension-api": ["../../src/extensionAPI.ts"],
|
||||
"openclaw/plugin-sdk": ["../../dist/plugin-sdk/index.d.ts"],
|
||||
"openclaw/plugin-sdk/*": ["../../dist/plugin-sdk/*.d.ts"],
|
||||
"openclaw/plugin-sdk/account-id": ["../../dist/plugin-sdk/account-id.d.ts"],
|
||||
"openclaw/plugin-sdk/channel-entry-contract": [
|
||||
"../../packages/plugin-sdk/dist/src/plugin-sdk/channel-entry-contract.d.ts"
|
||||
],
|
||||
"openclaw/plugin-sdk/browser-maintenance": [
|
||||
"../../packages/plugin-sdk/dist/extensions/browser/browser-maintenance.d.ts"
|
||||
],
|
||||
"openclaw/plugin-sdk/provider-catalog-shared": [
|
||||
"../../packages/plugin-sdk/dist/src/plugin-sdk/provider-catalog-shared.d.ts"
|
||||
],
|
||||
"openclaw/plugin-sdk/provider-entry": [
|
||||
"../../packages/plugin-sdk/dist/src/plugin-sdk/provider-entry.d.ts"
|
||||
],
|
||||
"openclaw/plugin-sdk/secret-ref-runtime": [
|
||||
"../../dist/plugin-sdk/src/plugin-sdk/secret-ref-runtime.d.ts"
|
||||
],
|
||||
"@openclaw/*.js": ["../../packages/plugin-sdk/dist/extensions/*.d.ts", "../*"],
|
||||
"@openclaw/*": ["../*"],
|
||||
"@openclaw/plugin-sdk/*": ["../../dist/plugin-sdk/*.d.ts"],
|
||||
|
||||
@@ -864,6 +864,10 @@
|
||||
"types": "./dist/plugin-sdk/provider-http.d.ts",
|
||||
"default": "./dist/plugin-sdk/provider-http.js"
|
||||
},
|
||||
"./plugin-sdk/provider-model-types": {
|
||||
"types": "./dist/plugin-sdk/provider-model-types.d.ts",
|
||||
"default": "./dist/plugin-sdk/provider-model-types.js"
|
||||
},
|
||||
"./plugin-sdk/provider-model-shared": {
|
||||
"types": "./dist/plugin-sdk/provider-model-shared.d.ts",
|
||||
"default": "./dist/plugin-sdk/provider-model-shared.js"
|
||||
|
||||
@@ -18,22 +18,6 @@ export const EXTENSION_PACKAGE_BOUNDARY_BASE_PATHS = {
|
||||
"openclaw/extension-api": ["../src/extensionAPI.ts"],
|
||||
"openclaw/plugin-sdk": ["../dist/plugin-sdk/index.d.ts"],
|
||||
"openclaw/plugin-sdk/*": ["../dist/plugin-sdk/*.d.ts"],
|
||||
"openclaw/plugin-sdk/account-id": ["../dist/plugin-sdk/account-id.d.ts"],
|
||||
"openclaw/plugin-sdk/channel-entry-contract": [
|
||||
"../packages/plugin-sdk/dist/src/plugin-sdk/channel-entry-contract.d.ts",
|
||||
],
|
||||
"openclaw/plugin-sdk/browser-maintenance": [
|
||||
"../packages/plugin-sdk/dist/extensions/browser/browser-maintenance.d.ts",
|
||||
],
|
||||
"openclaw/plugin-sdk/provider-catalog-shared": [
|
||||
"../packages/plugin-sdk/dist/src/plugin-sdk/provider-catalog-shared.d.ts",
|
||||
],
|
||||
"openclaw/plugin-sdk/provider-entry": [
|
||||
"../packages/plugin-sdk/dist/src/plugin-sdk/provider-entry.d.ts",
|
||||
],
|
||||
"openclaw/plugin-sdk/secret-ref-runtime": [
|
||||
"../dist/plugin-sdk/src/plugin-sdk/secret-ref-runtime.d.ts",
|
||||
],
|
||||
"@openclaw/*.js": ["../packages/plugin-sdk/dist/extensions/*.d.ts", "../extensions/*"],
|
||||
"@openclaw/*": ["../packages/plugin-sdk/dist/extensions/*", "../extensions/*"],
|
||||
"@openclaw/plugin-sdk/*": ["../dist/plugin-sdk/*.d.ts"],
|
||||
|
||||
@@ -205,6 +205,7 @@
|
||||
"provider-entry",
|
||||
"provider-env-vars",
|
||||
"provider-http",
|
||||
"provider-model-types",
|
||||
"provider-model-shared",
|
||||
"volc-model-catalog-shared",
|
||||
"provider-onboard",
|
||||
|
||||
35
scripts/prepare-extension-package-boundary-artifacts.mjs
Normal file
35
scripts/prepare-extension-package-boundary-artifacts.mjs
Normal file
@@ -0,0 +1,35 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { createRequire } from "node:module";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const repoRoot = resolve(import.meta.dirname, "..");
|
||||
const tscBin = require.resolve("typescript/bin/tsc");
|
||||
|
||||
function runNodeStep(label, args, timeoutMs) {
|
||||
const result = spawnSync(process.execPath, args, {
|
||||
cwd: repoRoot,
|
||||
encoding: "utf8",
|
||||
maxBuffer: 16 * 1024 * 1024,
|
||||
timeout: timeoutMs,
|
||||
});
|
||||
|
||||
if (result.status === 0 && !result.error) {
|
||||
return;
|
||||
}
|
||||
|
||||
const timeoutSuffix =
|
||||
result.error?.name === "Error" && result.error.message.includes("ETIMEDOUT")
|
||||
? `\n${label} timed out after ${timeoutMs}ms`
|
||||
: "";
|
||||
const errorSuffix = result.error ? `\n${result.error.message}` : "";
|
||||
process.stderr.write(`${label}\n${result.stdout}${result.stderr}${timeoutSuffix}${errorSuffix}`);
|
||||
process.exit(result.status ?? 1);
|
||||
}
|
||||
|
||||
runNodeStep("plugin-sdk boundary dts", [tscBin, "-p", "tsconfig.plugin-sdk.dts.json"], 300_000);
|
||||
runNodeStep(
|
||||
"plugin-sdk boundary root shims",
|
||||
["--import", "tsx", resolve(repoRoot, "scripts/write-plugin-sdk-entry-dts.ts")],
|
||||
120_000,
|
||||
);
|
||||
@@ -1,5 +1,11 @@
|
||||
import { createProviderApiKeyAuthMethod } from "../plugins/provider-api-key-auth.js";
|
||||
import type { ProviderPlugin, ProviderPluginWizardSetup } from "../plugins/types.js";
|
||||
import type {
|
||||
ProviderPlugin,
|
||||
ProviderCatalogContext,
|
||||
ProviderCatalogResult,
|
||||
ProviderPluginCatalog,
|
||||
ProviderPluginWizardSetup,
|
||||
} from "../plugins/types.js";
|
||||
import { definePluginEntry } from "./plugin-entry.js";
|
||||
import type {
|
||||
OpenClawPluginApi,
|
||||
@@ -18,10 +24,19 @@ export type SingleProviderPluginApiKeyAuthOptions = Omit<
|
||||
wizard?: false | ProviderPluginWizardSetup;
|
||||
};
|
||||
|
||||
export type SingleProviderPluginCatalogOptions = {
|
||||
buildProvider: Parameters<typeof buildSingleProviderApiKeyCatalog>[0]["buildProvider"];
|
||||
allowExplicitBaseUrl?: boolean;
|
||||
};
|
||||
export type SingleProviderPluginCatalogOptions =
|
||||
| {
|
||||
buildProvider: Parameters<typeof buildSingleProviderApiKeyCatalog>[0]["buildProvider"];
|
||||
allowExplicitBaseUrl?: boolean;
|
||||
run?: never;
|
||||
order?: never;
|
||||
}
|
||||
| {
|
||||
run: ProviderPluginCatalog["run"];
|
||||
order?: ProviderPluginCatalog["order"];
|
||||
buildProvider?: never;
|
||||
allowExplicitBaseUrl?: never;
|
||||
};
|
||||
|
||||
export type SingleProviderPluginOptions = {
|
||||
id: string;
|
||||
@@ -111,6 +126,26 @@ export function defineSingleProviderPluginEntry(options: SingleProviderPluginOpt
|
||||
...(wizard ? { wizard } : {}),
|
||||
});
|
||||
});
|
||||
let catalog: ProviderPluginCatalog;
|
||||
if ("run" in provider.catalog) {
|
||||
const catalogRun = provider.catalog.run;
|
||||
catalog = {
|
||||
order: provider.catalog.order ?? "simple",
|
||||
run: catalogRun!,
|
||||
};
|
||||
} else {
|
||||
const buildProvider = provider.catalog.buildProvider;
|
||||
catalog = {
|
||||
order: "simple",
|
||||
run: (ctx: ProviderCatalogContext): Promise<ProviderCatalogResult> =>
|
||||
buildSingleProviderApiKeyCatalog({
|
||||
ctx,
|
||||
providerId,
|
||||
buildProvider,
|
||||
...(provider.catalog.allowExplicitBaseUrl ? { allowExplicitBaseUrl: true } : {}),
|
||||
}),
|
||||
};
|
||||
}
|
||||
api.registerProvider({
|
||||
id: providerId,
|
||||
label: provider.label,
|
||||
@@ -118,16 +153,7 @@ export function defineSingleProviderPluginEntry(options: SingleProviderPluginOpt
|
||||
...(provider.aliases ? { aliases: provider.aliases } : {}),
|
||||
...(envVars ? { envVars } : {}),
|
||||
auth,
|
||||
catalog: {
|
||||
order: "simple",
|
||||
run: (ctx) =>
|
||||
buildSingleProviderApiKeyCatalog({
|
||||
ctx,
|
||||
providerId,
|
||||
buildProvider: provider.catalog.buildProvider,
|
||||
...(provider.catalog.allowExplicitBaseUrl ? { allowExplicitBaseUrl: true } : {}),
|
||||
}),
|
||||
},
|
||||
catalog,
|
||||
...Object.fromEntries(
|
||||
Object.entries(provider).filter(
|
||||
([key]) =>
|
||||
|
||||
7
src/plugin-sdk/provider-model-types.ts
Normal file
7
src/plugin-sdk/provider-model-types.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export type {
|
||||
BedrockDiscoveryConfig,
|
||||
ModelApi,
|
||||
ModelCompatConfig,
|
||||
ModelDefinitionConfig,
|
||||
ModelProviderConfig,
|
||||
} from "../config/types.models.js";
|
||||
@@ -3,39 +3,53 @@ import { rmSync, writeFileSync } from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import { resolve } from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { collectOptInExtensionPackageBoundaries } from "../scripts/lib/extension-package-boundary.ts";
|
||||
import {
|
||||
collectOptInExtensionPackageBoundaries,
|
||||
readExtensionPackageBoundaryTsconfig,
|
||||
} from "../scripts/lib/extension-package-boundary.ts";
|
||||
|
||||
const REPO_ROOT = resolve(import.meta.dirname, "..");
|
||||
const PREPARE_BOUNDARY_ARTIFACTS_BIN = resolve(
|
||||
REPO_ROOT,
|
||||
"scripts/prepare-extension-package-boundary-artifacts.mjs",
|
||||
);
|
||||
const require = createRequire(import.meta.url);
|
||||
const TSC_BIN = require.resolve("typescript/bin/tsc");
|
||||
const PLUGIN_SDK_DTS_TSCONFIG = resolve(REPO_ROOT, "tsconfig.plugin-sdk.dts.json");
|
||||
const OPT_IN_EXTENSION_IDS = collectOptInExtensionPackageBoundaries(REPO_ROOT);
|
||||
const CANARY_EXTENSION_IDS = [
|
||||
...new Map(
|
||||
OPT_IN_EXTENSION_IDS.map((extensionId) => [
|
||||
JSON.stringify(readExtensionPackageBoundaryTsconfig(extensionId, REPO_ROOT)),
|
||||
extensionId,
|
||||
]),
|
||||
).values(),
|
||||
];
|
||||
|
||||
function runTsc(args: string[]) {
|
||||
return spawnSync(process.execPath, [TSC_BIN, ...args], {
|
||||
function runNode(args: string[], timeout: number) {
|
||||
return spawnSync(process.execPath, args, {
|
||||
cwd: REPO_ROOT,
|
||||
encoding: "utf8",
|
||||
maxBuffer: 16 * 1024 * 1024,
|
||||
timeout,
|
||||
});
|
||||
}
|
||||
|
||||
describe("opt-in extension package TypeScript boundaries", () => {
|
||||
it("typechecks each opt-in extension cleanly through @openclaw/plugin-sdk", () => {
|
||||
const prepareResult = runTsc(["-p", PLUGIN_SDK_DTS_TSCONFIG]);
|
||||
const prepareResult = runNode([PREPARE_BOUNDARY_ARTIFACTS_BIN], 420_000);
|
||||
expect(prepareResult.status, `${prepareResult.stdout}\n${prepareResult.stderr}`).toBe(0);
|
||||
|
||||
for (const extensionId of OPT_IN_EXTENSION_IDS) {
|
||||
const result = runTsc([
|
||||
"-p",
|
||||
resolve(REPO_ROOT, "extensions", extensionId, "tsconfig.json"),
|
||||
"--noEmit",
|
||||
]);
|
||||
const result = runNode(
|
||||
[TSC_BIN, "-p", resolve(REPO_ROOT, "extensions", extensionId, "tsconfig.json"), "--noEmit"],
|
||||
120_000,
|
||||
);
|
||||
expect(result.status, `${extensionId}\n${result.stdout}\n${result.stderr}`).toBe(0);
|
||||
}
|
||||
});
|
||||
}, 300_000);
|
||||
|
||||
it.each(OPT_IN_EXTENSION_IDS)(
|
||||
"fails when %s imports src/cli through a relative path",
|
||||
(extensionId) => {
|
||||
it("fails when opt-in extensions import src/cli through a relative path", () => {
|
||||
for (const extensionId of CANARY_EXTENSION_IDS) {
|
||||
const extensionRoot = resolve(REPO_ROOT, "extensions", extensionId);
|
||||
const canaryPath = resolve(extensionRoot, "__rootdir_boundary_canary__.ts");
|
||||
const tsconfigPath = resolve(extensionRoot, "tsconfig.rootdir-canary.json");
|
||||
@@ -60,7 +74,7 @@ describe("opt-in extension package TypeScript boundaries", () => {
|
||||
"utf8",
|
||||
);
|
||||
|
||||
const result = runTsc(["-p", tsconfigPath, "--noEmit"]);
|
||||
const result = runNode([TSC_BIN, "-p", tsconfigPath, "--noEmit"], 120_000);
|
||||
const output = `${result.stdout}\n${result.stderr}`;
|
||||
expect(result.status).not.toBe(0);
|
||||
expect(output).toContain("TS6059");
|
||||
@@ -69,6 +83,6 @@ describe("opt-in extension package TypeScript boundaries", () => {
|
||||
rmSync(canaryPath, { force: true });
|
||||
rmSync(tsconfigPath, { force: true });
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,10 +10,6 @@
|
||||
"rootDir": ".",
|
||||
"tsBuildInfoFile": "dist/plugin-sdk/.tsbuildinfo"
|
||||
},
|
||||
"include": [
|
||||
"src/plugin-sdk/**/*.ts",
|
||||
"src/types/**/*.d.ts",
|
||||
"packages/memory-host-sdk/src/**/*.ts"
|
||||
],
|
||||
"include": ["src/**/*.ts", "src/types/**/*.d.ts", "packages/memory-host-sdk/src/**/*.ts"],
|
||||
"exclude": ["node_modules", "dist", "src/**/*.test.ts"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user