fix(providers): load trusted external thinking policies

This commit is contained in:
Vincent Koc
2026-07-12 08:40:22 +08:00
committed by Vincent Koc
parent 43634f616b
commit 16a647041c
11 changed files with 457 additions and 80 deletions
+2 -22
View File
@@ -36,6 +36,7 @@ import { detectZaiEndpoint, type ZaiEndpointId } from "./detect.js";
import { zaiMediaUnderstandingProvider } from "./media-understanding-provider.js";
import { buildZaiModelDefinition, resolveZaiBaseUrl } from "./model-definitions.js";
import { applyZaiConfig, applyZaiProviderConfig, resolveZaiModelId } from "./onboard.js";
import { isGlm52ModelId, resolveThinkingProfile } from "./provider-policy-api.js";
const PROVIDER_ID = "zai";
const GLM5_TEMPLATE_MODEL_ID = "glm-4.7";
@@ -128,10 +129,6 @@ function isDisabledThinkingLevel(thinkingLevel: ProviderWrapStreamFnContext["thi
return thinkingLevel === "off";
}
function isGlm52ModelId(modelId?: string | null): boolean {
return normalizeLowercaseStringOrEmpty(modelId).startsWith("glm-5.2");
}
function mapThinkingLevelToZaiReasoningEffort(
thinkingLevel: ProviderWrapStreamFnContext["thinkingLevel"],
): "high" | "max" | undefined {
@@ -394,24 +391,7 @@ export default definePluginEntry({
}),
prepareExtraParams: (ctx) => defaultToolStreamExtraParams(ctx.extraParams),
wrapStreamFn: (ctx) => wrapZaiStreamFn(ctx),
resolveThinkingProfile: (ctx) =>
isGlm52ModelId(ctx.modelId)
? {
levels: [
{ id: "off", label: "off" },
{ id: "low", label: "low" },
{ id: "high", label: "high" },
{ id: "max", label: "max" },
],
defaultLevel: "off",
}
: {
levels: [
{ id: "off", label: "off" },
{ id: "low", label: "on" },
],
defaultLevel: "off",
},
resolveThinkingProfile,
isModernModelRef: ({ modelId }) => {
const lower = normalizeLowercaseStringOrEmpty(modelId);
return (
@@ -0,0 +1,27 @@
// Z.AI tests cover its cold provider thinking policy.
import { describe, expect, it } from "vitest";
import { resolveThinkingProfile } from "./provider-policy-api.js";
describe("zai provider thinking policy", () => {
it.each(["glm-5.2", "glm-5.2-flash"])("exposes full GLM 5.2 levels for %s", (modelId) => {
expect(resolveThinkingProfile({ provider: "zai", modelId })).toEqual({
levels: [
{ id: "off", label: "off" },
{ id: "low", label: "low" },
{ id: "high", label: "high" },
{ id: "max", label: "max" },
],
defaultLevel: "off",
});
});
it.each(["glm-5.1", "glm-4.7"])("keeps older GLM models binary for %s", (modelId) => {
expect(resolveThinkingProfile({ provider: "zai", modelId })).toEqual({
levels: [
{ id: "off", label: "off" },
{ id: "low", label: "on" },
],
defaultLevel: "off",
});
});
});
+33
View File
@@ -0,0 +1,33 @@
// Z.AI public policy surface shared by cold selection and the provider runtime.
import type {
ProviderDefaultThinkingPolicyContext,
ProviderThinkingProfile,
} from "openclaw/plugin-sdk/plugin-entry";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
export function isGlm52ModelId(modelId?: string | null): boolean {
return normalizeLowercaseStringOrEmpty(modelId).startsWith("glm-5.2");
}
export function resolveThinkingProfile(
ctx: ProviderDefaultThinkingPolicyContext,
): ProviderThinkingProfile {
if (isGlm52ModelId(ctx.modelId)) {
return {
levels: [
{ id: "off", label: "off" },
{ id: "low", label: "low" },
{ id: "high", label: "high" },
{ id: "max", label: "max" },
],
defaultLevel: "off",
};
}
return {
levels: [
{ id: "off", label: "off" },
{ id: "low", label: "on" },
],
defaultLevel: "off",
};
}
+49 -17
View File
@@ -16,7 +16,10 @@ import type {
ProviderDefaultThinkingPolicyContext,
ProviderThinkingProfile,
} from "./provider-thinking.types.js";
import { loadBundledPluginPublicArtifactModuleSync } from "./public-surface-loader.js";
import {
loadBundledPluginPublicArtifactModuleSync,
loadPluginPublicArtifactModuleSync,
} from "./public-surface-loader.js";
const PROVIDER_POLICY_ARTIFACT_CANDIDATES = ["provider-policy-api.js"] as const;
const providerPolicySurfaceByPluginId = new Map<string, BundledProviderPolicySurface | null>();
@@ -52,35 +55,64 @@ function hasProviderPolicyHook(
);
}
/** Loads policy hooks directly by canonical bundled plugin id. */
export function resolveDirectBundledProviderPolicySurface(
pluginId: string,
): BundledProviderPolicySurface | null {
const cacheKey = `${resolveBundledPluginsDir() ?? ""}\0${pluginId}`;
const cached = providerPolicySurfaceByPluginId.get(cacheKey);
function resolveCachedProviderPolicySurface(params: {
cacheKey: string;
loadModule: (artifactBasename: string) => Record<string, unknown>;
missingSurfacePrefix: string;
}): BundledProviderPolicySurface | null {
const cached = providerPolicySurfaceByPluginId.get(params.cacheKey);
if (cached !== undefined) {
return cached;
}
for (const artifactBasename of PROVIDER_POLICY_ARTIFACT_CANDIDATES) {
try {
const mod = loadBundledPluginPublicArtifactModuleSync<Record<string, unknown>>({
dirName: pluginId,
artifactBasename,
});
const mod = params.loadModule(artifactBasename);
if (hasProviderPolicyHook(mod)) {
providerPolicySurfaceByPluginId.set(cacheKey, mod);
providerPolicySurfaceByPluginId.set(params.cacheKey, mod);
return mod;
}
} catch (error) {
if (
error instanceof Error &&
error.message.startsWith("Unable to resolve bundled plugin public surface ")
) {
if (error instanceof Error && error.message.startsWith(params.missingSurfacePrefix)) {
continue;
}
throw error;
}
}
providerPolicySurfaceByPluginId.set(cacheKey, null);
providerPolicySurfaceByPluginId.set(params.cacheKey, null);
return null;
}
/** Loads policy hooks directly by canonical bundled plugin id. */
export function resolveDirectBundledProviderPolicySurface(
pluginId: string,
): BundledProviderPolicySurface | null {
return resolveCachedProviderPolicySurface({
cacheKey: `${resolveBundledPluginsDir() ?? ""}\0${pluginId}`,
loadModule: (artifactBasename) =>
loadBundledPluginPublicArtifactModuleSync<Record<string, unknown>>({
dirName: pluginId,
artifactBasename,
}),
missingSurfacePrefix: "Unable to resolve bundled plugin public surface ",
});
}
/** Loads policy hooks from a host-verified official external plugin install. */
export function resolveTrustedExternalProviderPolicySurface(params: {
pluginId: string;
pluginRoot: string;
trustedOfficialInstall?: boolean;
}): BundledProviderPolicySurface | null {
if (params.trustedOfficialInstall !== true) {
return null;
}
return resolveCachedProviderPolicySurface({
cacheKey: `${params.pluginRoot}\0${params.pluginId}`,
loadModule: (artifactBasename) =>
loadPluginPublicArtifactModuleSync<Record<string, unknown>>({
pluginRoot: params.pluginRoot,
artifactBasename,
}),
missingSurfacePrefix: "Unable to resolve plugin public surface ",
});
}
+87 -2
View File
@@ -5,13 +5,33 @@ import path from "node:path";
import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { ModelProviderConfig } from "../config/types.models.js";
import { resolveBundledProviderPolicySurface } from "./provider-public-artifacts.js";
import {
resolveBundledProviderPolicySurface,
resolveProviderPolicySurface,
} from "./provider-public-artifacts.js";
function writeExternalPolicyFixture(): string {
const pluginRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-provider-policy-external-"));
fs.writeFileSync(
path.join(pluginRoot, "provider-policy-api.js"),
[
"export function resolveThinkingProfile({ modelId }) {",
' return modelId === "full"',
' ? { levels: [{ id: "off" }, { id: "high" }, { id: "max" }], defaultLevel: "off" }',
' : { levels: [{ id: "off" }, { id: "low", label: "on" }], defaultLevel: "off" };',
"}",
"",
].join("\n"),
"utf8",
);
return pluginRoot;
}
describe("provider public artifacts", () => {
const originalBundledPluginsDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
const originalTrustBundledPluginsDir = process.env.OPENCLAW_TEST_TRUST_BUNDLED_PLUGINS_DIR;
afterEach(() => {
function restoreBundledPluginEnv() {
if (originalBundledPluginsDir === undefined) {
delete process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
} else {
@@ -22,6 +42,10 @@ describe("provider public artifacts", () => {
} else {
process.env.OPENCLAW_TEST_TRUST_BUNDLED_PLUGINS_DIR = originalTrustBundledPluginsDir;
}
}
afterEach(() => {
restoreBundledPluginEnv();
vi.doUnmock("./bundled-dir.js");
vi.doUnmock("./manifest-registry.js");
vi.doUnmock("./public-surface-loader.js");
@@ -98,6 +122,67 @@ describe("provider public artifacts", () => {
});
});
it("loads trusted official external provider policy before runtime registration", () => {
const bundledPluginsDir = fs.mkdtempSync(
path.join(os.tmpdir(), "openclaw-empty-bundled-plugins-"),
);
const pluginRoot = writeExternalPolicyFixture();
try {
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = bundledPluginsDir;
process.env.OPENCLAW_TEST_TRUST_BUNDLED_PLUGINS_DIR = "1";
const fixturePlugin = {
id: "fixture-provider",
origin: "external",
trustedOfficialInstall: true,
rootDir: pluginRoot,
providers: ["fixture-provider"],
cliBackends: [],
} as const;
const surface = resolveProviderPolicySurface("fixture-provider", {
manifestRegistry: { plugins: [fixturePlugin as never] },
});
expect(
surface
?.resolveThinkingProfile?.({ provider: "fixture-provider", modelId: "full" })
?.levels.map((level) => level.id),
).toEqual(["off", "high", "max"]);
expect(
surface
?.resolveThinkingProfile?.({ provider: "fixture-provider", modelId: "legacy" })
?.levels.map((level) => level.label),
).toEqual([undefined, "on"]);
} finally {
restoreBundledPluginEnv();
fs.rmSync(pluginRoot, { recursive: true, force: true });
fs.rmSync(bundledPluginsDir, { recursive: true, force: true });
}
});
it("does not load public policy code from untrusted external plugins", () => {
const pluginRoot = writeExternalPolicyFixture();
try {
expect(
resolveProviderPolicySurface("fixture-provider", {
manifestRegistry: {
plugins: [
{
id: "fixture-provider",
origin: "external",
rootDir: pluginRoot,
providers: ["fixture-provider"],
cliBackends: [],
} as never,
],
},
}),
).toBeNull();
} finally {
fs.rmSync(pluginRoot, { recursive: true, force: true });
}
});
it("resolves multi-provider policy artifacts by manifest-owned provider id", async () => {
const bundledPluginsDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-provider-policy-"));
const pluginDir = path.join(bundledPluginsDir, "openai");
+34
View File
@@ -4,6 +4,7 @@ import { resolveBundledPluginsDir } from "./bundled-dir.js";
import { loadPluginManifestRegistry, type PluginManifestRegistry } from "./manifest-registry.js";
import {
resolveDirectBundledProviderPolicySurface,
resolveTrustedExternalProviderPolicySurface,
type BundledProviderPolicySurface,
} from "./provider-policy-surface.js";
@@ -78,3 +79,36 @@ export function resolveBundledProviderPolicySurface(
}
return resolveDirectBundledProviderPolicySurface(ownerPluginId);
}
/** Resolves provider policy hooks from bundled or trusted official plugin artifacts. */
export function resolveProviderPolicySurface(
providerId: string,
options: { manifestRegistry?: Pick<PluginManifestRegistry, "plugins"> } = {},
): BundledProviderPolicySurface | null {
const bundledSurface = resolveBundledProviderPolicySurface(providerId, options);
if (bundledSurface) {
return bundledSurface;
}
const normalizedProviderId = normalizeProviderId(providerId);
if (!normalizedProviderId || !options.manifestRegistry) {
return null;
}
for (const plugin of options.manifestRegistry.plugins.toSorted((left, right) =>
left.id.localeCompare(right.id),
)) {
if (
pluginOwnsProviderPolicyRef(plugin, normalizedProviderId) &&
plugin.trustedOfficialInstall === true
) {
const surface = resolveTrustedExternalProviderPolicySurface({
pluginId: plugin.id,
pluginRoot: plugin.rootDir,
trustedOfficialInstall: plugin.trustedOfficialInstall,
});
if (surface) {
return surface;
}
}
}
return null;
}
@@ -0,0 +1,90 @@
// Verifies cold thinking policy resolution for trusted external providers.
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterAll, describe, expect, it, vi } from "vitest";
const fixtureState = vi.hoisted(() => ({ pluginRoot: "" }));
const originalBundledPluginsDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
const originalTrustBundledPluginsDir = process.env.OPENCLAW_TEST_TRUST_BUNDLED_PLUGINS_DIR;
const emptyBundledPluginsDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-empty-plugins-"));
const externalPluginRoot = fs.mkdtempSync(
path.join(os.tmpdir(), "openclaw-provider-policy-external-"),
);
fs.writeFileSync(
path.join(externalPluginRoot, "provider-policy-api.js"),
[
"export function resolveThinkingProfile({ modelId }) {",
' return modelId === "full"',
' ? { levels: [{ id: "off" }, { id: "high" }, { id: "max" }], defaultLevel: "off" }',
' : { levels: [{ id: "off" }, { id: "low", label: "on" }], defaultLevel: "off" };',
"}",
"",
].join("\n"),
"utf8",
);
fixtureState.pluginRoot = externalPluginRoot;
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = emptyBundledPluginsDir;
process.env.OPENCLAW_TEST_TRUST_BUNDLED_PLUGINS_DIR = "1";
vi.mock("./current-plugin-metadata-snapshot.js", () => ({
getCurrentPluginMetadataSnapshot: () => ({
manifestRegistry: {
plugins: [
{
id: "fixture-provider",
origin: "external",
trustedOfficialInstall: true,
rootDir: fixtureState.pluginRoot,
providers: ["fixture-provider"],
cliBackends: [],
},
],
},
}),
}));
const { isThinkingLevelSupported, listThinkingLevels, resolveThinkingDefaultForModel } =
await import("../auto-reply/thinking.js");
afterAll(() => {
if (originalBundledPluginsDir === undefined) {
delete process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
} else {
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = originalBundledPluginsDir;
}
if (originalTrustBundledPluginsDir === undefined) {
delete process.env.OPENCLAW_TEST_TRUST_BUNDLED_PLUGINS_DIR;
} else {
process.env.OPENCLAW_TEST_TRUST_BUNDLED_PLUGINS_DIR = originalTrustBundledPluginsDir;
}
fs.rmSync(emptyBundledPluginsDir, { recursive: true, force: true });
fs.rmSync(externalPluginRoot, { recursive: true, force: true });
});
describe("trusted external provider thinking policy", () => {
it("resolves a full profile without activating the provider runtime", () => {
expect(listThinkingLevels("fixture-provider", "full")).toEqual(["off", "high", "max"]);
expect(
isThinkingLevelSupported({
provider: "fixture-provider",
model: "full",
level: "max",
}),
).toBe(true);
expect(resolveThinkingDefaultForModel({ provider: "fixture-provider", model: "full" })).toBe(
"off",
);
});
it("keeps the fixture legacy model on its binary profile", () => {
expect(listThinkingLevels("fixture-provider", "legacy")).toEqual(["off", "low"]);
expect(
isThinkingLevelSupported({
provider: "fixture-provider",
model: "legacy",
level: "high",
}),
).toBe(false);
});
});
+13 -2
View File
@@ -1,6 +1,7 @@
// Resolves provider thinking-level policy from plugin metadata.
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import { resolveBundledProviderPolicySurface } from "./provider-public-artifacts.js";
import { getCurrentPluginMetadataSnapshot } from "./current-plugin-metadata-snapshot.js";
import { resolveProviderPolicySurface } from "./provider-public-artifacts.js";
import type {
ProviderDefaultThinkingPolicyContext,
ProviderThinkingProfile,
@@ -57,6 +58,16 @@ function resolveActiveThinkingProvider(providerId: string): ThinkingProviderPlug
return undefined;
}
function resolveProviderPublicPolicySurface(providerId: string) {
const metadataSnapshot = getCurrentPluginMetadataSnapshot({
allowScopedSnapshot: true,
allowWorkspaceScopedSnapshot: true,
});
return resolveProviderPolicySurface(providerId, {
manifestRegistry: metadataSnapshot?.manifestRegistry,
});
}
type ThinkingHookParams<TContext> = {
provider: string;
context: TContext;
@@ -86,7 +97,7 @@ export function resolveProviderThinkingProfile(
if (activeProfile !== undefined) {
return activeProfile;
}
return resolveBundledProviderPolicySurface(params.provider)?.resolveThinkingProfile?.(
return resolveProviderPublicPolicySurface(params.provider)?.resolveThinkingProfile?.(
params.context,
);
}
+68 -37
View File
@@ -11,7 +11,10 @@ import {
getCachedPluginModuleLoader,
type PluginModuleLoaderCache,
} from "./plugin-module-loader-cache.js";
import { resolveBundledPluginPublicSurfacePath } from "./public-surface-runtime.js";
import {
resolveBundledPluginPublicSurfacePath,
resolvePluginRootPublicSurfacePath,
} from "./public-surface-runtime.js";
import { resolvePluginLoaderTryNative, resolveLoaderPackageRoot } from "./sdk-alias.js";
const OPENCLAW_PACKAGE_ROOT =
@@ -114,6 +117,49 @@ function loadPublicSurfaceModule(modulePath: string): unknown {
return getModuleLoader(modulePath)(modulePath);
}
function loadValidatedPublicSurfaceModule(params: {
modulePath: string;
boundaryRoot: string;
boundaryLabel: string;
surfaceLabel: string;
}): object {
const cached = publicSurfaceModuleCache.get(params.modulePath);
if (cached) {
return cached as object;
}
const opened = openRootFileSync({
absolutePath: params.modulePath,
rootPath: params.boundaryRoot,
boundaryLabel: params.boundaryLabel,
rejectHardlinks: false,
});
if (!opened.ok) {
throw new Error(`Unable to open ${params.surfaceLabel}`, { cause: opened.error });
}
const validatedPath = opened.path;
const validatedStat = opened.stat;
fs.closeSync(opened.fd);
const currentStat = fs.statSync(validatedPath);
if (!sameFileIdentity(validatedStat, currentStat)) {
throw new Error(`${params.surfaceLabel} changed after validation`);
}
const sentinel: Record<string, unknown> = {};
publicSurfaceModuleCache.set(params.modulePath, sentinel);
publicSurfaceModuleCache.set(validatedPath, sentinel);
try {
const loaded = loadPublicSurfaceModule(validatedPath) as object;
Object.assign(sentinel, loaded);
return sentinel;
} catch (error) {
publicSurfaceModuleCache.delete(params.modulePath);
publicSurfaceModuleCache.delete(validatedPath);
throw error;
}
}
// oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- Dynamic public artifact loaders use caller-supplied module surface types.
export function loadBundledPluginPublicArtifactModuleSync<T extends object>(params: {
dirName: string;
@@ -125,47 +171,32 @@ export function loadBundledPluginPublicArtifactModuleSync<T extends object>(para
`Unable to resolve bundled plugin public surface ${params.dirName}/${params.artifactBasename}`,
);
}
const cached = publicSurfaceModuleCache.get(location.modulePath);
if (cached) {
return cached as T;
}
const opened = openRootFileSync({
absolutePath: location.modulePath,
rootPath: location.boundaryRoot,
return loadValidatedPublicSurfaceModule({
modulePath: location.modulePath,
boundaryRoot: location.boundaryRoot,
boundaryLabel:
location.boundaryRoot === OPENCLAW_PACKAGE_ROOT ? "OpenClaw package root" : "plugin root",
rejectHardlinks: false,
});
if (!opened.ok) {
surfaceLabel: `bundled plugin public surface ${params.dirName}/${params.artifactBasename}`,
}) as T;
}
// oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- Dynamic public artifact loaders use caller-supplied module surface types.
export function loadPluginPublicArtifactModuleSync<T extends object>(params: {
pluginRoot: string;
artifactBasename: string;
}): T {
const modulePath = resolvePluginRootPublicSurfacePath(params);
if (!modulePath) {
throw new Error(
`Unable to open bundled plugin public surface ${params.dirName}/${params.artifactBasename}`,
{ cause: opened.error },
`Unable to resolve plugin public surface ${params.pluginRoot}/${params.artifactBasename}`,
);
}
const validatedPath = opened.path;
const validatedStat = opened.stat;
fs.closeSync(opened.fd);
const currentStat = fs.statSync(validatedPath);
if (!sameFileIdentity(validatedStat, currentStat)) {
throw new Error(
`Bundled plugin public surface changed after validation: ${params.dirName}/${params.artifactBasename}`,
);
}
const sentinel = {} as T;
publicSurfaceModuleCache.set(location.modulePath, sentinel);
publicSurfaceModuleCache.set(validatedPath, sentinel);
try {
const loaded = loadPublicSurfaceModule(validatedPath) as T;
Object.assign(sentinel, loaded);
return sentinel;
} catch (error) {
publicSurfaceModuleCache.delete(location.modulePath);
publicSurfaceModuleCache.delete(validatedPath);
throw error;
}
return loadValidatedPublicSurfaceModule({
modulePath,
boundaryRoot: path.resolve(params.pluginRoot),
boundaryLabel: "plugin root",
surfaceLabel: `plugin public surface ${params.artifactBasename}`,
}) as T;
}
/** Loads the first resolvable bundled public artifact from an ordered candidate list. */
+25
View File
@@ -75,6 +75,31 @@ export function resolveBundledPluginSourcePublicSurfacePath(params: {
return null;
}
/** Resolves a public surface artifact within one installed plugin root. */
export function resolvePluginRootPublicSurfacePath(params: {
pluginRoot: string;
artifactBasename: string;
}): string | null {
const artifactBasename = normalizeBundledPluginArtifactSubpath(params.artifactBasename);
const pluginRoot = path.resolve(params.pluginRoot);
for (const candidate of [
path.join(pluginRoot, artifactBasename),
path.join(pluginRoot, "dist", artifactBasename),
]) {
if (fs.existsSync(candidate)) {
return candidate;
}
}
const sourceBaseName = artifactBasename.replace(/\.js$/u, "");
for (const ext of PUBLIC_SURFACE_SOURCE_EXTENSIONS) {
const candidate = path.join(pluginRoot, `${sourceBaseName}${ext}`);
if (fs.existsSync(candidate)) {
return candidate;
}
}
return null;
}
function resolvePackageFallbackForBundledDir(params: {
rootDir: string;
bundledPluginsDir: string;
@@ -102,4 +102,33 @@ describe("plugin npm runtime build checks", () => {
},
]);
});
it("builds top-level public policy surfaces as standalone package artifacts", async () => {
const repoRoot = mkdtempSync(join(tmpdir(), "openclaw-plugin-runtime-check-"));
tempDirs.push(repoRoot);
writePackage(repoRoot, "provider-plugin", { publishToNpm: true }, ["./index.ts"]);
const packageDir = join(repoRoot, "extensions", "provider-plugin");
writeFileSync(join(packageDir, "index.ts"), "export default {};\n");
writeFileSync(
join(packageDir, "provider-policy-api.ts"),
"export function resolveThinkingProfile() { return { levels: [] }; }\n",
);
await expect(
checkPluginNpmRuntimeBuilds({
repoRoot,
packageDirs: ["extensions/provider-plugin"],
}),
).resolves.toEqual([
{
pluginDir: "provider-plugin",
status: "built",
entryCount: 2,
copiedStaticAssets: [],
},
]);
expect(await import(join(packageDir, "dist", "provider-policy-api.js"))).toHaveProperty(
"resolveThinkingProfile",
);
});
});