refactor: keep plugin sdk owner seams explicit

This commit is contained in:
Peter Steinberger
2026-04-27 12:50:25 +01:00
parent 189535308f
commit eaae63d288
10 changed files with 192 additions and 16 deletions

View File

@@ -87,6 +87,8 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview)
| Subpath | Key exports | | Subpath | Key exports |
| --- | --- | | --- | --- |
| `plugin-sdk/provider-entry` | `defineSingleProviderPluginEntry` | | `plugin-sdk/provider-entry` | `defineSingleProviderPluginEntry` |
| `plugin-sdk/lmstudio` | Supported LM Studio provider facade for setup, catalog discovery, and runtime model preparation |
| `plugin-sdk/lmstudio-runtime` | Supported LM Studio runtime facade for local server defaults, model discovery, request headers, and loaded-model helpers |
| `plugin-sdk/provider-setup` | Curated local/self-hosted provider setup helpers | | `plugin-sdk/provider-setup` | Curated local/self-hosted provider setup helpers |
| `plugin-sdk/self-hosted-provider-setup` | Focused OpenAI-compatible self-hosted provider setup helpers | | `plugin-sdk/self-hosted-provider-setup` | Focused OpenAI-compatible self-hosted provider setup helpers |
| `plugin-sdk/cli-backend` | CLI backend defaults + watchdog constants | | `plugin-sdk/cli-backend` | CLI backend defaults + watchdog constants |
@@ -150,6 +152,7 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview)
| --- | --- | | --- | --- |
| `plugin-sdk/runtime` | Broad runtime/logging/backup/plugin-install helpers | | `plugin-sdk/runtime` | Broad runtime/logging/backup/plugin-install helpers |
| `plugin-sdk/runtime-env` | Narrow runtime env, logger, timeout, retry, and backoff helpers | | `plugin-sdk/runtime-env` | Narrow runtime env, logger, timeout, retry, and backoff helpers |
| `plugin-sdk/browser-config` | Supported browser config facade for normalized profile/defaults, CDP URL parsing, and browser-control auth helpers |
| `plugin-sdk/channel-runtime-context` | Generic channel runtime-context registration and lookup helpers | | `plugin-sdk/channel-runtime-context` | Generic channel runtime-context registration and lookup helpers |
| `plugin-sdk/runtime-store` | `createPluginRuntimeStore` | | `plugin-sdk/runtime-store` | `createPluginRuntimeStore` |
| `plugin-sdk/plugin-runtime` | Shared plugin command/hook/http/interactive helpers | | `plugin-sdk/plugin-runtime` | Shared plugin command/hook/http/interactive helpers |
@@ -273,8 +276,8 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview)
| Matrix | `plugin-sdk/matrix`, `plugin-sdk/matrix-helper`, `plugin-sdk/matrix-runtime-heavy`, `plugin-sdk/matrix-runtime-shared`, `plugin-sdk/matrix-runtime-surface`, `plugin-sdk/matrix-surface`, `plugin-sdk/matrix-thread-bindings` | Bundled Matrix helper/runtime surface | | Matrix | `plugin-sdk/matrix`, `plugin-sdk/matrix-helper`, `plugin-sdk/matrix-runtime-heavy`, `plugin-sdk/matrix-runtime-shared`, `plugin-sdk/matrix-runtime-surface`, `plugin-sdk/matrix-surface`, `plugin-sdk/matrix-thread-bindings` | Bundled Matrix helper/runtime surface |
| Line | `plugin-sdk/line`, `plugin-sdk/line-core`, `plugin-sdk/line-runtime`, `plugin-sdk/line-surface` | Bundled LINE helper/runtime surface | | Line | `plugin-sdk/line`, `plugin-sdk/line-core`, `plugin-sdk/line-runtime`, `plugin-sdk/line-surface` | Bundled LINE helper/runtime surface |
| IRC | `plugin-sdk/irc`, `plugin-sdk/irc-surface` | Bundled IRC helper surface | | IRC | `plugin-sdk/irc`, `plugin-sdk/irc-surface` | Bundled IRC helper surface |
| Channel-specific helpers | `plugin-sdk/googlechat`, `plugin-sdk/googlechat-runtime-shared`, `plugin-sdk/zalouser`, `plugin-sdk/bluebubbles`, `plugin-sdk/bluebubbles-policy`, `plugin-sdk/mattermost`, `plugin-sdk/mattermost-policy`, `plugin-sdk/feishu`, `plugin-sdk/feishu-conversation`, `plugin-sdk/feishu-setup`, `plugin-sdk/msteams`, `plugin-sdk/nextcloud-talk`, `plugin-sdk/nostr`, `plugin-sdk/tlon`, `plugin-sdk/twitch`, `plugin-sdk/zalo`, `plugin-sdk/zalo-setup` | Deprecated bundled channel compatibility/helper seams. New plugins should import generic SDK subpaths or plugin-local barrels. | | Channel-specific helpers | `plugin-sdk/googlechat`, `plugin-sdk/googlechat-runtime-shared`, `plugin-sdk/zalouser`, `plugin-sdk/bluebubbles`, `plugin-sdk/bluebubbles-policy`, `plugin-sdk/mattermost`, `plugin-sdk/mattermost-policy`, `plugin-sdk/feishu`, `plugin-sdk/feishu-conversation`, `plugin-sdk/feishu-setup`, `plugin-sdk/msteams`, `plugin-sdk/nextcloud-talk`, `plugin-sdk/nostr`, `plugin-sdk/telegram-command-ui`, `plugin-sdk/tlon`, `plugin-sdk/twitch`, `plugin-sdk/zalo`, `plugin-sdk/zalo-setup` | Deprecated bundled channel compatibility/helper seams. New plugins should import generic SDK subpaths or plugin-local barrels. |
| Auth/plugin-specific helpers | `plugin-sdk/github-copilot-login`, `plugin-sdk/github-copilot-token`, `plugin-sdk/diagnostics-otel`, `plugin-sdk/diagnostics-prometheus`, `plugin-sdk/diffs`, `plugin-sdk/llm-task`, `plugin-sdk/memory-core`, `plugin-sdk/memory-lancedb`, `plugin-sdk/thread-ownership`, `plugin-sdk/voice-call` | Bundled feature/plugin helper seams; `plugin-sdk/github-copilot-token` currently exports `DEFAULT_COPILOT_API_BASE_URL`, `deriveCopilotApiBaseUrlFromToken`, and `resolveCopilotApiToken` | | Auth/plugin-specific helpers | `plugin-sdk/github-copilot-login`, `plugin-sdk/github-copilot-token`, `plugin-sdk/diagnostics-otel`, `plugin-sdk/diagnostics-prometheus`, `plugin-sdk/diffs`, `plugin-sdk/llm-task`, `plugin-sdk/memory-core`, `plugin-sdk/memory-lancedb`, `plugin-sdk/opencode`, `plugin-sdk/thread-ownership`, `plugin-sdk/voice-call` | Bundled feature/plugin helper seams; `plugin-sdk/github-copilot-token` currently exports `DEFAULT_COPILOT_API_BASE_URL`, `deriveCopilotApiBaseUrlFromToken`, and `resolveCopilotApiToken` |
</Accordion> </Accordion>
</AccordionGroup> </AccordionGroup>

View File

@@ -1,4 +1,4 @@
import type { PluginRuntime } from "openclaw/plugin-sdk/bluebubbles"; import type { PluginRuntime } from "openclaw/plugin-sdk/core";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import "./test-mocks.js"; import "./test-mocks.js";
import { import {

View File

@@ -1,4 +1,5 @@
import type { HistoryEntry, PluginRuntime } from "openclaw/plugin-sdk/bluebubbles"; import type { PluginRuntime } from "openclaw/plugin-sdk/core";
import type { HistoryEntry } from "openclaw/plugin-sdk/reply-history";
import { vi } from "vitest"; import { vi } from "vitest";
import { createPluginRuntimeMock } from "../../../../test/helpers/plugins/plugin-runtime-mock.js"; import { createPluginRuntimeMock } from "../../../../test/helpers/plugins/plugin-runtime-mock.js";
import { _resetBlueBubblesInboundDedupForTest } from "../inbound-dedupe.js"; import { _resetBlueBubblesInboundDedupForTest } from "../inbound-dedupe.js";

View File

@@ -1,7 +1,7 @@
import { Command } from "commander"; import { Command } from "commander";
import { formatZonedTimestamp } from "openclaw/plugin-sdk/matrix-runtime-shared";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { registerMatrixCli, resetMatrixCliStateForTests } from "./cli.js"; import { registerMatrixCli, resetMatrixCliStateForTests } from "./cli.js";
import { formatZonedTimestamp } from "./runtime-api.js";
import type { CoreConfig } from "./types.js"; import type { CoreConfig } from "./types.js";
const bootstrapMatrixVerificationMock = vi.fn(); const bootstrapMatrixVerificationMock = vi.fn();

View File

@@ -1,6 +1,7 @@
import { ServerResponse, type IncomingMessage } from "node:http"; import { ServerResponse, type IncomingMessage } from "node:http";
import { PassThrough } from "node:stream"; import { PassThrough } from "node:stream";
import type { OpenClawConfig, RuntimeEnv } from "openclaw/plugin-sdk/mattermost"; import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime";
import { beforeEach, describe, expect, it, vi } from "vitest"; import { beforeEach, describe, expect, it, vi } from "vitest";
import type { ResolvedMattermostAccount } from "./accounts.js"; import type { ResolvedMattermostAccount } from "./accounts.js";

View File

@@ -1,5 +1,5 @@
import { createOpencodeCatalogApiKeyAuthMethod } from "openclaw/plugin-sdk/opencode";
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"; import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key";
import { PASSTHROUGH_GEMINI_REPLAY_HOOKS } from "openclaw/plugin-sdk/provider-model-shared"; import { PASSTHROUGH_GEMINI_REPLAY_HOOKS } from "openclaw/plugin-sdk/provider-model-shared";
import { applyOpencodeGoConfig, OPENCODE_GO_DEFAULT_MODEL_REF } from "./api.js"; import { applyOpencodeGoConfig, OPENCODE_GO_DEFAULT_MODEL_REF } from "./api.js";
import { opencodeGoMediaUnderstandingProvider } from "./media-understanding-provider.js"; import { opencodeGoMediaUnderstandingProvider } from "./media-understanding-provider.js";
@@ -11,6 +11,14 @@ import {
import { createOpencodeGoDeepSeekV4Wrapper } from "./stream.js"; import { createOpencodeGoDeepSeekV4Wrapper } from "./stream.js";
const PROVIDER_ID = "opencode-go"; const PROVIDER_ID = "opencode-go";
const OPENCODE_SHARED_PROFILE_IDS = ["opencode:default", "opencode-go:default"] as const;
const OPENCODE_SHARED_HINT = "Shared API key for Zen + Go catalogs";
const OPENCODE_SHARED_WIZARD_GROUP = {
groupId: "opencode",
groupLabel: "OpenCode",
groupHint: OPENCODE_SHARED_HINT,
} as const;
export default definePluginEntry({ export default definePluginEntry({
id: PROVIDER_ID, id: PROVIDER_ID,
name: "OpenCode Go Provider", name: "OpenCode Go Provider",
@@ -22,20 +30,30 @@ export default definePluginEntry({
docsPath: "/providers/models", docsPath: "/providers/models",
envVars: ["OPENCODE_API_KEY", "OPENCODE_ZEN_API_KEY"], envVars: ["OPENCODE_API_KEY", "OPENCODE_ZEN_API_KEY"],
auth: [ auth: [
createOpencodeCatalogApiKeyAuthMethod({ createProviderApiKeyAuthMethod({
providerId: PROVIDER_ID, providerId: PROVIDER_ID,
methodId: "api-key",
label: "OpenCode Go catalog", label: "OpenCode Go catalog",
hint: OPENCODE_SHARED_HINT,
optionKey: "opencodeGoApiKey", optionKey: "opencodeGoApiKey",
flagName: "--opencode-go-api-key", flagName: "--opencode-go-api-key",
envVar: "OPENCODE_API_KEY",
promptMessage: "Enter OpenCode API key",
profileIds: [...OPENCODE_SHARED_PROFILE_IDS],
defaultModel: OPENCODE_GO_DEFAULT_MODEL_REF, defaultModel: OPENCODE_GO_DEFAULT_MODEL_REF,
applyConfig: (cfg) => applyOpencodeGoConfig(cfg), applyConfig: (cfg) => applyOpencodeGoConfig(cfg),
expectedProviders: ["opencode", "opencode-go"],
noteMessage: [ noteMessage: [
"OpenCode uses one API key across the Zen and Go catalogs.", "OpenCode uses one API key across the Zen and Go catalogs.",
"Go focuses on Kimi, GLM, and MiniMax coding models.", "Go focuses on Kimi, GLM, and MiniMax coding models.",
"Get your API key at: https://opencode.ai/auth", "Get your API key at: https://opencode.ai/auth",
].join("\n"), ].join("\n"),
choiceId: "opencode-go", noteTitle: "OpenCode",
choiceLabel: "OpenCode Go catalog", wizard: {
choiceId: "opencode-go",
choiceLabel: "OpenCode Go catalog",
...OPENCODE_SHARED_WIZARD_GROUP,
},
}), }),
], ],
normalizeConfig: ({ providerConfig }) => { normalizeConfig: ({ providerConfig }) => {

View File

@@ -1,5 +1,5 @@
import { createOpencodeCatalogApiKeyAuthMethod } from "openclaw/plugin-sdk/opencode";
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"; import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key";
import { import {
matchesExactOrPrefix, matchesExactOrPrefix,
PASSTHROUGH_GEMINI_REPLAY_HOOKS, PASSTHROUGH_GEMINI_REPLAY_HOOKS,
@@ -10,6 +10,13 @@ import { opencodeMediaUnderstandingProvider } from "./media-understanding-provid
const PROVIDER_ID = "opencode"; const PROVIDER_ID = "opencode";
const MINIMAX_MODERN_MODEL_MATCHERS = ["minimax-m2.7"] as const; const MINIMAX_MODERN_MODEL_MATCHERS = ["minimax-m2.7"] as const;
const OPENCODE_SHARED_PROFILE_IDS = ["opencode:default", "opencode-go:default"] as const;
const OPENCODE_SHARED_HINT = "Shared API key for Zen + Go catalogs";
const OPENCODE_SHARED_WIZARD_GROUP = {
groupId: "opencode",
groupLabel: "OpenCode",
groupHint: OPENCODE_SHARED_HINT,
} as const;
function isModernOpencodeModel(modelId: string): boolean { function isModernOpencodeModel(modelId: string): boolean {
const lower = normalizeLowercaseStringOrEmpty(modelId); const lower = normalizeLowercaseStringOrEmpty(modelId);
@@ -30,21 +37,31 @@ export default definePluginEntry({
docsPath: "/providers/models", docsPath: "/providers/models",
envVars: ["OPENCODE_API_KEY", "OPENCODE_ZEN_API_KEY"], envVars: ["OPENCODE_API_KEY", "OPENCODE_ZEN_API_KEY"],
auth: [ auth: [
createOpencodeCatalogApiKeyAuthMethod({ createProviderApiKeyAuthMethod({
providerId: PROVIDER_ID, providerId: PROVIDER_ID,
methodId: "api-key",
label: "OpenCode Zen catalog", label: "OpenCode Zen catalog",
hint: OPENCODE_SHARED_HINT,
optionKey: "opencodeZenApiKey", optionKey: "opencodeZenApiKey",
flagName: "--opencode-zen-api-key", flagName: "--opencode-zen-api-key",
envVar: "OPENCODE_API_KEY",
promptMessage: "Enter OpenCode API key",
profileIds: [...OPENCODE_SHARED_PROFILE_IDS],
defaultModel: OPENCODE_ZEN_DEFAULT_MODEL, defaultModel: OPENCODE_ZEN_DEFAULT_MODEL,
applyConfig: (cfg) => applyOpencodeZenConfig(cfg), applyConfig: (cfg) => applyOpencodeZenConfig(cfg),
expectedProviders: ["opencode", "opencode-go"],
noteMessage: [ noteMessage: [
"OpenCode uses one API key across the Zen and Go catalogs.", "OpenCode uses one API key across the Zen and Go catalogs.",
"Zen provides access to Claude, GPT, Gemini, and more models.", "Zen provides access to Claude, GPT, Gemini, and more models.",
"Get your API key at: https://opencode.ai/auth", "Get your API key at: https://opencode.ai/auth",
"Choose the Zen catalog when you want the curated multi-model proxy.", "Choose the Zen catalog when you want the curated multi-model proxy.",
].join("\n"), ].join("\n"),
choiceId: "opencode-zen", noteTitle: "OpenCode",
choiceLabel: "OpenCode Zen catalog", wizard: {
choiceId: "opencode-zen",
choiceLabel: "OpenCode Zen catalog",
...OPENCODE_SHARED_WIZARD_GROUP,
},
}), }),
], ],
...PASSTHROUGH_GEMINI_REPLAY_HOOKS, ...PASSTHROUGH_GEMINI_REPLAY_HOOKS,

View File

@@ -1,5 +1,4 @@
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime"; import type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime";
import { buildCommandsPaginationKeyboard } from "openclaw/plugin-sdk/telegram-command-ui";
import { import {
buildBrowseProvidersButton, buildBrowseProvidersButton,
buildModelsKeyboard, buildModelsKeyboard,
@@ -7,7 +6,35 @@ import {
type ProviderInfo, type ProviderInfo,
} from "./model-buttons.js"; } from "./model-buttons.js";
export { buildCommandsPaginationKeyboard }; export function buildCommandsPaginationKeyboard(
currentPage: number,
totalPages: number,
agentId?: string,
): Array<Array<{ text: string; callback_data: string }>> {
const buttons: Array<{ text: string; callback_data: string }> = [];
const suffix = agentId ? `:${agentId}` : "";
if (currentPage > 1) {
buttons.push({
text: "◀ Prev",
callback_data: `commands_page_${currentPage - 1}${suffix}`,
});
}
buttons.push({
text: `${currentPage}/${totalPages}`,
callback_data: `commands_page_noop${suffix}`,
});
if (currentPage < totalPages) {
buttons.push({
text: "Next ▶",
callback_data: `commands_page_${currentPage + 1}${suffix}`,
});
}
return [buttons];
}
export function buildTelegramModelsMenuButtons(params: { providers: ProviderInfo[] }) { export function buildTelegramModelsMenuButtons(params: { providers: ProviderInfo[] }) {
return buildProviderKeyboard(params.providers); return buildProviderKeyboard(params.providers);

View File

@@ -47,6 +47,8 @@ export const reservedBundledPluginSdkEntrypoints = [
"msteams", "msteams",
"nextcloud-talk", "nextcloud-talk",
"nostr", "nostr",
"opencode",
"telegram-command-ui",
"thread-ownership", "thread-ownership",
"tlon", "tlon",
"twitch", "twitch",
@@ -64,6 +66,32 @@ export const supportedBundledFacadeSdkEntrypoints = [
"tts-runtime", "tts-runtime",
] as const; ] as const;
export const publicPluginOwnedSdkEntrypoints = [
"browser-config",
"image-generation-core",
"memory-core-host-engine-embeddings",
"memory-core-host-engine-foundation",
"memory-core-host-engine-qmd",
"memory-core-host-engine-storage",
"memory-core-host-events",
"memory-core-host-multimodal",
"memory-core-host-query",
"memory-core-host-runtime-cli",
"memory-core-host-runtime-core",
"memory-core-host-runtime-files",
"memory-core-host-secret",
"memory-core-host-status",
"memory-host-core",
"memory-host-events",
"memory-host-files",
"memory-host-markdown",
"memory-host-search",
"memory-host-status",
"speech-core",
"telegram-command-config",
"video-generation-core",
] as const;
/** Map every SDK entrypoint name to its source file path inside the repo. */ /** Map every SDK entrypoint name to its source file path inside the repo. */
export function buildPluginSdkEntrySources(entries: readonly string[] = pluginSdkEntrypoints) { export function buildPluginSdkEntrySources(entries: readonly string[] = pluginSdkEntrypoints) {
return Object.fromEntries(entries.map((entry) => [entry, `src/plugin-sdk/${entry}.ts`])); return Object.fromEntries(entries.map((entry) => [entry, `src/plugin-sdk/${entry}.ts`]));

View File

@@ -4,12 +4,14 @@ import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { import {
pluginSdkEntrypoints, pluginSdkEntrypoints,
publicPluginOwnedSdkEntrypoints,
reservedBundledPluginSdkEntrypoints, reservedBundledPluginSdkEntrypoints,
supportedBundledFacadeSdkEntrypoints, supportedBundledFacadeSdkEntrypoints,
} from "../../plugin-sdk/entrypoints.js"; } from "../../plugin-sdk/entrypoints.js";
const ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "../.."); const ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
const REPO_ROOT = resolve(ROOT_DIR, ".."); const REPO_ROOT = resolve(ROOT_DIR, "..");
const SDK_SUBPATH_DOC_FILE = "docs/plugins/sdk-subpaths.md";
const PUBLIC_CONTRACT_REFERENCE_FILES = [ const PUBLIC_CONTRACT_REFERENCE_FILES = [
"docs/plugins/architecture.md", "docs/plugins/architecture.md",
"src/plugins/contracts/plugin-sdk-subpaths.test.ts", "src/plugins/contracts/plugin-sdk-subpaths.test.ts",
@@ -57,6 +59,48 @@ function collectPluginSdkSubpathReferences() {
return references; return references;
} }
function collectDocumentedSdkSubpaths(): Set<string> {
const source = readFileSync(resolve(REPO_ROOT, SDK_SUBPATH_DOC_FILE), "utf8");
return new Set(
[...source.matchAll(/`plugin-sdk\/([a-z0-9][a-z0-9-]*)`/g)]
.map((match) => match[1])
.filter((subpath): subpath is string => Boolean(subpath)),
);
}
function collectBundledPluginIds(): string[] {
return readdirSync(resolve(REPO_ROOT, "extensions"), { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.map((entry) => entry.name)
.toSorted((a, b) => b.length - a.length || a.localeCompare(b));
}
function collectPluginOwnedSdkEntrypoints(): string[] {
const pluginIds = collectBundledPluginIds();
return pluginSdkEntrypoints
.filter((entrypoint) =>
pluginIds.some(
(pluginId) => entrypoint === pluginId || entrypoint.startsWith(`${pluginId}-`),
),
)
.toSorted();
}
function collectClassificationOverlaps(classifications: Record<string, readonly string[]>) {
const seen = new Map<string, string[]>();
for (const [classification, entrypoints] of Object.entries(classifications)) {
for (const entrypoint of entrypoints) {
const current = seen.get(entrypoint) ?? [];
current.push(classification);
seen.set(entrypoint, current);
}
}
return [...seen.entries()]
.filter(([, matches]) => matches.length > 1)
.map(([entrypoint, matches]) => `${entrypoint}: ${matches.toSorted().join(", ")}`)
.toSorted();
}
function collectBundledFacadeSdkEntrypoints(): string[] { function collectBundledFacadeSdkEntrypoints(): string[] {
const entrypoints: string[] = []; const entrypoints: string[] = [];
for (const entrypoint of pluginSdkEntrypoints) { for (const entrypoint of pluginSdkEntrypoints) {
@@ -224,6 +268,43 @@ describe("plugin-sdk package contract guardrails", () => {
}); });
}); });
it("keeps plugin-owned SDK subpaths explicitly classified and documented", () => {
const entrypoints = new Set(pluginSdkEntrypoints);
const reserved = new Set<string>(reservedBundledPluginSdkEntrypoints);
const supported = new Set<string>(supportedBundledFacadeSdkEntrypoints);
const publicOwned = new Set<string>(publicPluginOwnedSdkEntrypoints);
const documented = collectDocumentedSdkSubpaths();
const pluginOwnedEntrypoints = collectPluginOwnedSdkEntrypoints();
const classified = new Set([...reserved, ...supported, ...publicOwned]);
const unknownPublicOwned = [...publicOwned].filter(
(entrypoint) => !entrypoints.has(entrypoint),
);
const classificationOverlaps = collectClassificationOverlaps({
reserved: reservedBundledPluginSdkEntrypoints,
supported: supportedBundledFacadeSdkEntrypoints,
publicOwned: publicPluginOwnedSdkEntrypoints,
});
const unclassifiedPluginOwned = pluginOwnedEntrypoints.filter(
(entrypoint) => !classified.has(entrypoint),
);
const undocumentedPluginOwned = pluginOwnedEntrypoints.filter(
(entrypoint) => !documented.has(entrypoint),
);
expect({
unknownPublicOwned,
classificationOverlaps,
unclassifiedPluginOwned,
undocumentedPluginOwned,
}).toEqual({
unknownPublicOwned: [],
classificationOverlaps: [],
unclassifiedPluginOwned: [],
undocumentedPluginOwned: [],
});
});
it("keeps curated public plugin-sdk references on exported built subpaths", () => { it("keeps curated public plugin-sdk references on exported built subpaths", () => {
const entrypoints = new Set(pluginSdkEntrypoints); const entrypoints = new Set(pluginSdkEntrypoints);
const exports = new Set(collectPluginSdkPackageExports()); const exports = new Set(collectPluginSdkPackageExports());