refactor: share plugin config issue formatting

This commit is contained in:
Peter Steinberger
2026-04-20 23:34:06 +01:00
parent 6464cf4756
commit 0094f76314
5 changed files with 60 additions and 50 deletions

View File

@@ -1,8 +1,8 @@
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { formatPluginConfigIssue } from "openclaw/plugin-sdk/extension-shared";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import type { z } from "openclaw/plugin-sdk/zod";
import { AcpxPluginConfigSchema, DEFAULT_ACPX_TIMEOUT_SECONDS } from "./config-schema.js";
import type {
AcpxPluginConfig,
@@ -112,26 +112,13 @@ type ParseResult =
| { ok: true; value: AcpxPluginConfig | undefined }
| { ok: false; message: string };
function formatAcpxConfigIssue(issue: z.ZodIssue | undefined): string {
if (!issue) {
return "invalid config";
}
if (issue.code === "unrecognized_keys" && issue.keys.length > 0) {
return `unknown config key: ${issue.keys[0]}`;
}
if (issue.code === "invalid_type" && issue.path.length === 0) {
return "expected config object";
}
return issue.message;
}
function parseAcpxPluginConfig(value: unknown): ParseResult {
if (value === undefined) {
return { ok: true, value: undefined };
}
const parsed = AcpxPluginConfigSchema.safeParse(value);
if (!parsed.success) {
return { ok: false, message: formatAcpxConfigIssue(parsed.error.issues[0]) };
return { ok: false, message: formatPluginConfigIssue(parsed.error.issues[0]) };
}
return {
ok: true,

View File

@@ -1,3 +1,4 @@
import { mapPluginConfigIssues } from "openclaw/plugin-sdk/extension-shared";
import { buildPluginConfigSchema } from "openclaw/plugin-sdk/plugin-entry";
import { z } from "openclaw/plugin-sdk/zod";
import type { OpenClawPluginConfigSchema } from "../api.js";
@@ -180,13 +181,7 @@ const diffsPluginConfigSchemaBase = buildPluginConfigSchema(DiffsPluginJsonSchem
return {
success: false,
error: {
issues: result.error.issues.map((issue) => ({
path: issue.path.filter((segment): segment is string | number => {
const kind = typeof segment;
return kind === "string" || kind === "number";
}),
message: issue.message,
})),
issues: mapPluginConfigIssues(result.error.issues),
},
};
},

View File

@@ -1,5 +1,6 @@
import os from "node:os";
import path from "node:path";
import { mapPluginConfigIssues } from "openclaw/plugin-sdk/extension-shared";
import { buildPluginConfigSchema, z, type OpenClawPluginConfigSchema } from "../api.js";
export const WIKI_VAULT_MODES = ["isolated", "bridge", "unsafe-local"] as const;
@@ -174,13 +175,7 @@ const memoryWikiConfigSchemaBase = buildPluginConfigSchema(MemoryWikiConfigSourc
return {
success: false,
error: {
issues: result.error.issues.map((issue) => ({
path: issue.path.filter((segment): segment is string | number => {
const kind = typeof segment;
return kind === "string" || kind === "number";
}),
message: issue.message,
})),
issues: mapPluginConfigIssues(result.error.issues),
},
};
},

View File

@@ -1,5 +1,9 @@
import path from "node:path";
import { buildPluginConfigSchema, type OpenClawPluginConfigSchema } from "openclaw/plugin-sdk/core";
import {
formatPluginConfigIssue,
mapPluginConfigIssues,
} from "openclaw/plugin-sdk/extension-shared";
import { z } from "openclaw/plugin-sdk/zod";
export type OpenShellPluginConfig = {
@@ -91,19 +95,6 @@ const OpenShellPluginConfigSchema = z.strictObject({
.optional(),
});
function formatOpenShellConfigIssue(issue: z.ZodIssue | undefined): string {
if (!issue) {
return "invalid config";
}
if (issue.code === "unrecognized_keys" && issue.keys.length > 0) {
return `unknown config key: ${issue.keys[0]}`;
}
if (issue.code === "invalid_type" && issue.path.length === 0) {
return "expected config object";
}
return issue.message;
}
function isManagedOpenShellRemotePath(value: string): boolean {
return OPEN_SHELL_MANAGED_REMOTE_ROOTS.some(
(root) => value === root || value.startsWith(`${root}/`),
@@ -141,13 +132,7 @@ export function createOpenShellPluginConfigSchema(): OpenClawPluginConfigSchema
return {
success: false,
error: {
issues: parsed.error.issues.map((issue) => ({
path: issue.path.filter((segment): segment is string | number => {
const kind = typeof segment;
return kind === "string" || kind === "number";
}),
message: formatOpenShellConfigIssue(issue),
})),
issues: mapPluginConfigIssues(parsed.error.issues),
},
};
},
@@ -176,7 +161,7 @@ export function resolveOpenShellPluginConfig(value: unknown): ResolvedOpenShellP
const parsed = OpenShellPluginConfigSchema.safeParse(value);
if (!parsed.success) {
const message = formatOpenShellConfigIssue(parsed.error.issues[0]);
const message = formatPluginConfigIssue(parsed.error.issues[0]);
throw new Error(`Invalid openshell plugin config: ${message}`);
}
const cfg = parsed.data as OpenShellPluginConfig;

View File

@@ -143,6 +143,54 @@ const DEFAULT_PACKAGE_JSON_VERSION_CANDIDATES = [
type PackageJsonRequire = (id: string) => unknown;
type PluginConfigIssuePathSegment = string | number;
type PluginConfigIssue = {
path: PluginConfigIssuePathSegment[];
message: string;
};
type PluginConfigIssueMessageOptions = {
invalidConfigMessage?: string;
unknownKeyMessage?: (key: string) => string;
rootInvalidTypeMessage?: string;
};
export function formatPluginConfigIssue(
issue: z.ZodIssue | undefined,
options?: PluginConfigIssueMessageOptions,
): string {
if (!issue) {
return options?.invalidConfigMessage ?? "invalid config";
}
if (issue.code === "unrecognized_keys" && issue.keys.length > 0) {
return options?.unknownKeyMessage?.(issue.keys[0]) ?? `unknown config key: ${issue.keys[0]}`;
}
if (issue.code === "invalid_type" && issue.path.length === 0) {
return options?.rootInvalidTypeMessage ?? "expected config object";
}
return issue.message;
}
export function normalizePluginConfigIssuePath(
path: readonly unknown[],
): PluginConfigIssuePathSegment[] {
return path.filter((segment): segment is PluginConfigIssuePathSegment => {
const kind = typeof segment;
return kind === "string" || kind === "number";
});
}
export function mapPluginConfigIssues(
issues: readonly z.ZodIssue[],
options?: PluginConfigIssueMessageOptions,
): PluginConfigIssue[] {
return issues.map((issue) => ({
path: normalizePluginConfigIssuePath(issue.path),
message: formatPluginConfigIssue(issue, options),
}));
}
export function readPluginPackageVersion(params: {
require: PackageJsonRequire;
candidates?: readonly string[];