mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 09:41:11 +00:00
refactor: dedupe plugin string list helpers
This commit is contained in:
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
||||
import { z } from "zod";
|
||||
import { loadPluginManifestRegistry } from "../plugins/manifest-registry.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { normalizeTrimmedStringList } from "../shared/string-normalization.js";
|
||||
import { note } from "../terminal/note.js";
|
||||
import { shortenHomePath } from "../utils.js";
|
||||
import { safeParseJsonWithSchema, safeParseWithSchema } from "../utils/zod-parse.js";
|
||||
@@ -22,13 +23,6 @@ type LegacyManifestContractMigration = {
|
||||
|
||||
const JsonRecordSchema = z.record(z.string(), z.unknown());
|
||||
|
||||
function normalizeStringList(value: unknown): string[] {
|
||||
if (!Array.isArray(value)) {
|
||||
return [];
|
||||
}
|
||||
return value.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean);
|
||||
}
|
||||
|
||||
function readManifestJson(manifestPath: string): Record<string, unknown> | null {
|
||||
try {
|
||||
return safeParseJsonWithSchema(JsonRecordSchema, fs.readFileSync(manifestPath, "utf-8"));
|
||||
@@ -50,8 +44,8 @@ function buildLegacyManifestContractMigration(params: {
|
||||
if (!(key in params.raw)) {
|
||||
continue;
|
||||
}
|
||||
const legacyValues = normalizeStringList(params.raw[key]);
|
||||
const contractValues = normalizeStringList(nextContracts[key]);
|
||||
const legacyValues = normalizeTrimmedStringList(params.raw[key]);
|
||||
const contractValues = normalizeTrimmedStringList(nextContracts[key]);
|
||||
if (legacyValues.length > 0 && contractValues.length === 0) {
|
||||
nextContracts[key] = legacyValues;
|
||||
changeLines.push(
|
||||
|
||||
@@ -18,9 +18,9 @@ import {
|
||||
import { buildChannelAccountBindings, resolvePreferredAccountId } from "../routing/bindings.js";
|
||||
import { normalizeAgentId } from "../routing/session-key.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
|
||||
import { asNullableRecord } from "../shared/record-coerce.js";
|
||||
import { styleHealthChannelLine } from "../terminal/health-style.js";
|
||||
import { isRich } from "../terminal/theme.js";
|
||||
import { isRecord } from "../utils.js";
|
||||
import { logGatewayConnectionDetails } from "./status.gateway-connection.js";
|
||||
|
||||
export type ChannelAccountHealthSummary = {
|
||||
@@ -164,9 +164,6 @@ const buildSessionSummary = (storePath: string) => {
|
||||
} satisfies HealthSummary["sessions"];
|
||||
};
|
||||
|
||||
const asRecord = (value: unknown): Record<string, unknown> | null =>
|
||||
isRecord(value) ? value : null;
|
||||
|
||||
async function inspectHealthAccount(plugin: ChannelPlugin, cfg: OpenClawConfig, accountId: string) {
|
||||
return (
|
||||
plugin.config.inspectAccount?.(cfg, accountId) ??
|
||||
@@ -179,7 +176,7 @@ async function inspectHealthAccount(plugin: ChannelPlugin, cfg: OpenClawConfig,
|
||||
}
|
||||
|
||||
function readBooleanField(value: unknown, key: string): boolean | undefined {
|
||||
const record = asRecord(value);
|
||||
const record = asNullableRecord(value);
|
||||
if (!record) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -246,7 +243,7 @@ async function resolveHealthAccountContext(params: {
|
||||
}
|
||||
|
||||
const formatProbeLine = (probe: unknown, opts: { botUsernames?: string[] } = {}): string | null => {
|
||||
const record = asRecord(probe);
|
||||
const record = asNullableRecord(probe);
|
||||
if (!record) {
|
||||
return null;
|
||||
}
|
||||
@@ -257,9 +254,9 @@ const formatProbeLine = (probe: unknown, opts: { botUsernames?: string[] } = {})
|
||||
const elapsedMs = typeof record.elapsedMs === "number" ? record.elapsedMs : null;
|
||||
const status = typeof record.status === "number" ? record.status : null;
|
||||
const error = typeof record.error === "string" ? record.error : null;
|
||||
const bot = asRecord(record.bot);
|
||||
const bot = asNullableRecord(record.bot);
|
||||
const botUsername = bot && typeof bot.username === "string" ? bot.username : null;
|
||||
const webhook = asRecord(record.webhook);
|
||||
const webhook = asNullableRecord(record.webhook);
|
||||
const webhookUrl = webhook && typeof webhook.url === "string" ? webhook.url : null;
|
||||
|
||||
const usernames = new Set<string>();
|
||||
@@ -293,7 +290,7 @@ const formatProbeLine = (probe: unknown, opts: { botUsernames?: string[] } = {})
|
||||
};
|
||||
|
||||
const formatAccountProbeTiming = (summary: ChannelAccountHealthSummary): string | null => {
|
||||
const probe = asRecord(summary.probe);
|
||||
const probe = asNullableRecord(summary.probe);
|
||||
if (!probe) {
|
||||
return null;
|
||||
}
|
||||
@@ -304,7 +301,7 @@ const formatAccountProbeTiming = (summary: ChannelAccountHealthSummary): string
|
||||
}
|
||||
|
||||
const accountId = summary.accountId || "default";
|
||||
const botRecord = asRecord(probe.bot);
|
||||
const botRecord = asNullableRecord(probe.bot);
|
||||
const botUsername =
|
||||
botRecord && typeof botRecord.username === "string" ? botRecord.username : null;
|
||||
const handle = botUsername ? `@${botUsername}` : accountId;
|
||||
@@ -314,7 +311,7 @@ const formatAccountProbeTiming = (summary: ChannelAccountHealthSummary): string
|
||||
};
|
||||
|
||||
const isProbeFailure = (summary: ChannelAccountHealthSummary): boolean => {
|
||||
const probe = asRecord(summary.probe);
|
||||
const probe = asNullableRecord(summary.probe);
|
||||
if (!probe) {
|
||||
return false;
|
||||
}
|
||||
@@ -359,8 +356,8 @@ export const formatHealthChannelLines = (
|
||||
const botUsernames = listSummaries
|
||||
? listSummaries
|
||||
.map((account) => {
|
||||
const probeRecord = asRecord(account.probe);
|
||||
const bot = probeRecord ? asRecord(probeRecord.bot) : null;
|
||||
const probeRecord = asNullableRecord(account.probe);
|
||||
const bot = probeRecord ? asNullableRecord(probeRecord.bot) : null;
|
||||
return bot && typeof bot.username === "string" ? bot.username : null;
|
||||
})
|
||||
.filter((value): value is string => Boolean(value))
|
||||
@@ -668,7 +665,7 @@ export async function healthCommand(
|
||||
cfg,
|
||||
accountId,
|
||||
});
|
||||
const record = asRecord(account);
|
||||
const record = asNullableRecord(account);
|
||||
const tokenSource =
|
||||
record && typeof record.tokenSource === "string" ? record.tokenSource : undefined;
|
||||
runtime.log(
|
||||
@@ -690,8 +687,8 @@ export async function healthCommand(
|
||||
for (const [channelId, channelSummary] of Object.entries(summary.channels ?? {})) {
|
||||
const accounts = channelSummary.accounts ?? {};
|
||||
const probes = Object.entries(accounts).map(([accountId, accountSummary]) => {
|
||||
const probe = asRecord(accountSummary.probe);
|
||||
const bot = probe ? asRecord(probe.bot) : null;
|
||||
const probe = asNullableRecord(accountSummary.probe);
|
||||
const bot = probe ? asNullableRecord(probe.bot) : null;
|
||||
const username = bot && typeof bot.username === "string" ? bot.username : null;
|
||||
return `${accountId}=${username ?? "(no bot)"}`;
|
||||
});
|
||||
|
||||
@@ -19,7 +19,7 @@ import type {
|
||||
import { inspectReadOnlyChannelAccount } from "../../channels/read-only-account-inspect.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { sha256HexPrefix } from "../../logging/redact-identifier.js";
|
||||
import { isRecord } from "../../utils.js";
|
||||
import { asRecord } from "../../shared/record-coerce.js";
|
||||
import { formatTimeAgo } from "./format.js";
|
||||
|
||||
export type ChannelRow = {
|
||||
@@ -45,8 +45,6 @@ type ResolvedChannelAccountRowParams = {
|
||||
accountId: string;
|
||||
};
|
||||
|
||||
const asRecord = (value: unknown): Record<string, unknown> => (isRecord(value) ? value : {});
|
||||
|
||||
function summarizeSources(sources: Array<string | undefined>): {
|
||||
label: string;
|
||||
parts: string[];
|
||||
|
||||
@@ -3,7 +3,10 @@ import path from "node:path";
|
||||
import { createJiti } from "jiti";
|
||||
import { buildChannelConfigSchema } from "../channels/plugins/config-schema.js";
|
||||
import type { ChannelConfigRuntimeSchema } from "../channels/plugins/types.plugin.js";
|
||||
import { trimBundledPluginString } from "./bundled-plugin-scan.js";
|
||||
import {
|
||||
normalizeBundledPluginStringList,
|
||||
trimBundledPluginString,
|
||||
} from "./bundled-plugin-scan.js";
|
||||
import type {
|
||||
OpenClawPackageManifest,
|
||||
PluginManifest,
|
||||
@@ -35,13 +38,6 @@ type ChannelConfigSurface = {
|
||||
|
||||
const jitiLoaders = new Map<string, ReturnType<typeof createJiti>>();
|
||||
|
||||
function normalizeStringList(value: unknown): string[] {
|
||||
if (!Array.isArray(value)) {
|
||||
return [];
|
||||
}
|
||||
return value.map((entry) => trimBundledPluginString(entry) ?? "").filter(Boolean);
|
||||
}
|
||||
|
||||
function isBuiltChannelConfigSchema(value: unknown): value is ChannelConfigSurface {
|
||||
if (!value || typeof value !== "object") {
|
||||
return false;
|
||||
@@ -138,7 +134,7 @@ export function collectBundledChannelConfigs(params: {
|
||||
manifest: PluginManifest;
|
||||
packageManifest?: OpenClawPackageManifest;
|
||||
}): Record<string, PluginManifestChannelConfig> | undefined {
|
||||
const channelIds = normalizeStringList(params.manifest.channels);
|
||||
const channelIds = normalizeBundledPluginStringList(params.manifest.channels);
|
||||
const existingChannelConfigs: Record<string, PluginManifestChannelConfig> =
|
||||
params.manifest.channelConfigs && Object.keys(params.manifest.channelConfigs).length > 0
|
||||
? { ...params.manifest.channelConfigs }
|
||||
@@ -153,7 +149,7 @@ export function collectBundledChannelConfigs(params: {
|
||||
for (const channelId of channelIds) {
|
||||
const existing = existingChannelConfigs[channelId];
|
||||
const channelMeta = resolvePackageChannelMeta(params.packageManifest, channelId);
|
||||
const preferOver = normalizeStringList(channelMeta?.preferOver);
|
||||
const preferOver = normalizeBundledPluginStringList(channelMeta?.preferOver);
|
||||
const uiHints: Record<string, PluginConfigUiHint> | undefined =
|
||||
surface?.uiHints || existing?.uiHints
|
||||
? {
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
collectBundledPluginPublicSurfaceArtifacts,
|
||||
collectBundledPluginRuntimeSidecarArtifacts,
|
||||
deriveBundledPluginIdHint,
|
||||
normalizeBundledPluginStringList,
|
||||
rewriteBundledPluginEntryToBuiltPath,
|
||||
resolveBundledPluginScanDir,
|
||||
trimBundledPluginString,
|
||||
@@ -54,13 +55,6 @@ export function clearBundledPluginMetadataCache(): void {
|
||||
bundledPluginMetadataCache.clear();
|
||||
}
|
||||
|
||||
function normalizeStringList(value: unknown): string[] {
|
||||
if (!Array.isArray(value)) {
|
||||
return [];
|
||||
}
|
||||
return value.map((entry) => trimBundledPluginString(entry) ?? "").filter(Boolean);
|
||||
}
|
||||
|
||||
function readPackageManifest(pluginDir: string): PackageManifest | undefined {
|
||||
const packagePath = path.join(pluginDir, "package.json");
|
||||
if (!fs.existsSync(packagePath)) {
|
||||
@@ -100,7 +94,7 @@ function collectBundledPluginMetadataForPackageRoot(
|
||||
|
||||
const packageJson = readPackageManifest(pluginDir);
|
||||
const packageManifest = getPackageManifestMetadata(packageJson);
|
||||
const extensions = normalizeStringList(packageManifest?.extensions);
|
||||
const extensions = normalizeBundledPluginStringList(packageManifest?.extensions);
|
||||
if (extensions.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,16 @@ export function trimBundledPluginString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
||||
}
|
||||
|
||||
export function normalizeBundledPluginStringList(value: unknown): string[] {
|
||||
if (!Array.isArray(value)) {
|
||||
return [];
|
||||
}
|
||||
return value.flatMap((entry) => {
|
||||
const normalized = trimBundledPluginString(entry);
|
||||
return normalized ? [normalized] : [];
|
||||
});
|
||||
}
|
||||
|
||||
export function rewriteBundledPluginEntryToBuiltPath(
|
||||
entry: string | undefined,
|
||||
): string | undefined {
|
||||
|
||||
@@ -4,6 +4,7 @@ import JSON5 from "json5";
|
||||
import type { ChannelConfigRuntimeSchema } from "../channels/plugins/types.plugin.js";
|
||||
import { MANIFEST_KEY } from "../compat/legacy-names.js";
|
||||
import { matchBoundaryFileOpenFailure, openBoundaryFileSync } from "../infra/boundary-file-read.js";
|
||||
import { normalizeTrimmedStringList } from "../shared/string-normalization.js";
|
||||
import { isRecord } from "../utils.js";
|
||||
import type { PluginConfigUiHint, PluginKind } from "./types.js";
|
||||
|
||||
@@ -163,13 +164,6 @@ export type PluginManifestLoadResult =
|
||||
| { ok: true; manifest: PluginManifest; manifestPath: string }
|
||||
| { ok: false; error: string; manifestPath: string };
|
||||
|
||||
function normalizeStringList(value: unknown): string[] {
|
||||
if (!Array.isArray(value)) {
|
||||
return [];
|
||||
}
|
||||
return value.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean);
|
||||
}
|
||||
|
||||
function normalizeStringListRecord(value: unknown): Record<string, string[]> | undefined {
|
||||
if (!isRecord(value)) {
|
||||
return undefined;
|
||||
@@ -180,7 +174,7 @@ function normalizeStringListRecord(value: unknown): Record<string, string[]> | u
|
||||
if (!providerId) {
|
||||
continue;
|
||||
}
|
||||
const values = normalizeStringList(rawValues);
|
||||
const values = normalizeTrimmedStringList(rawValues);
|
||||
if (values.length === 0) {
|
||||
continue;
|
||||
}
|
||||
@@ -194,17 +188,19 @@ function normalizeManifestContracts(value: unknown): PluginManifestContracts | u
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const memoryEmbeddingProviders = normalizeStringList(value.memoryEmbeddingProviders);
|
||||
const speechProviders = normalizeStringList(value.speechProviders);
|
||||
const realtimeTranscriptionProviders = normalizeStringList(value.realtimeTranscriptionProviders);
|
||||
const realtimeVoiceProviders = normalizeStringList(value.realtimeVoiceProviders);
|
||||
const mediaUnderstandingProviders = normalizeStringList(value.mediaUnderstandingProviders);
|
||||
const imageGenerationProviders = normalizeStringList(value.imageGenerationProviders);
|
||||
const videoGenerationProviders = normalizeStringList(value.videoGenerationProviders);
|
||||
const musicGenerationProviders = normalizeStringList(value.musicGenerationProviders);
|
||||
const webFetchProviders = normalizeStringList(value.webFetchProviders);
|
||||
const webSearchProviders = normalizeStringList(value.webSearchProviders);
|
||||
const tools = normalizeStringList(value.tools);
|
||||
const memoryEmbeddingProviders = normalizeTrimmedStringList(value.memoryEmbeddingProviders);
|
||||
const speechProviders = normalizeTrimmedStringList(value.speechProviders);
|
||||
const realtimeTranscriptionProviders = normalizeTrimmedStringList(
|
||||
value.realtimeTranscriptionProviders,
|
||||
);
|
||||
const realtimeVoiceProviders = normalizeTrimmedStringList(value.realtimeVoiceProviders);
|
||||
const mediaUnderstandingProviders = normalizeTrimmedStringList(value.mediaUnderstandingProviders);
|
||||
const imageGenerationProviders = normalizeTrimmedStringList(value.imageGenerationProviders);
|
||||
const videoGenerationProviders = normalizeTrimmedStringList(value.videoGenerationProviders);
|
||||
const musicGenerationProviders = normalizeTrimmedStringList(value.musicGenerationProviders);
|
||||
const webFetchProviders = normalizeTrimmedStringList(value.webFetchProviders);
|
||||
const webSearchProviders = normalizeTrimmedStringList(value.webSearchProviders);
|
||||
const tools = normalizeTrimmedStringList(value.tools);
|
||||
const contracts = {
|
||||
...(memoryEmbeddingProviders.length > 0 ? { memoryEmbeddingProviders } : {}),
|
||||
...(speechProviders.length > 0 ? { speechProviders } : {}),
|
||||
@@ -309,8 +305,8 @@ function normalizeManifestModelSupport(value: unknown): PluginManifestModelSuppo
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const modelPrefixes = normalizeStringList(value.modelPrefixes);
|
||||
const modelPatterns = normalizeStringList(value.modelPatterns);
|
||||
const modelPrefixes = normalizeTrimmedStringList(value.modelPrefixes);
|
||||
const modelPatterns = normalizeTrimmedStringList(value.modelPatterns);
|
||||
const modelSupport = {
|
||||
...(modelPrefixes.length > 0 ? { modelPrefixes } : {}),
|
||||
...(modelPatterns.length > 0 ? { modelPatterns } : {}),
|
||||
@@ -346,7 +342,7 @@ function normalizeProviderAuthChoices(
|
||||
entry.assistantVisibility === "manual-only" || entry.assistantVisibility === "visible"
|
||||
? entry.assistantVisibility
|
||||
: undefined;
|
||||
const deprecatedChoiceIds = normalizeStringList(entry.deprecatedChoiceIds);
|
||||
const deprecatedChoiceIds = normalizeTrimmedStringList(entry.deprecatedChoiceIds);
|
||||
const groupId = typeof entry.groupId === "string" ? entry.groupId.trim() : "";
|
||||
const groupLabel = typeof entry.groupLabel === "string" ? entry.groupLabel.trim() : "";
|
||||
const groupHint = typeof entry.groupHint === "string" ? entry.groupHint.trim() : "";
|
||||
@@ -355,7 +351,7 @@ function normalizeProviderAuthChoices(
|
||||
const cliOption = typeof entry.cliOption === "string" ? entry.cliOption.trim() : "";
|
||||
const cliDescription =
|
||||
typeof entry.cliDescription === "string" ? entry.cliDescription.trim() : "";
|
||||
const onboardingScopes = normalizeStringList(entry.onboardingScopes).filter(
|
||||
const onboardingScopes = normalizeTrimmedStringList(entry.onboardingScopes).filter(
|
||||
(scope): scope is PluginManifestOnboardingScope =>
|
||||
scope === "text-inference" || scope === "image-generation",
|
||||
);
|
||||
@@ -406,7 +402,7 @@ function normalizeChannelConfigs(
|
||||
: undefined;
|
||||
const label = typeof rawEntry.label === "string" ? rawEntry.label.trim() : "";
|
||||
const description = typeof rawEntry.description === "string" ? rawEntry.description.trim() : "";
|
||||
const preferOver = normalizeStringList(rawEntry.preferOver);
|
||||
const preferOver = normalizeTrimmedStringList(rawEntry.preferOver);
|
||||
normalized[channelId] = {
|
||||
schema,
|
||||
...(uiHints ? { uiHints } : {}),
|
||||
@@ -490,21 +486,21 @@ export function loadPluginManifest(
|
||||
|
||||
const kind = parsePluginKind(raw.kind);
|
||||
const enabledByDefault = raw.enabledByDefault === true;
|
||||
const legacyPluginIds = normalizeStringList(raw.legacyPluginIds);
|
||||
const autoEnableWhenConfiguredProviders = normalizeStringList(
|
||||
const legacyPluginIds = normalizeTrimmedStringList(raw.legacyPluginIds);
|
||||
const autoEnableWhenConfiguredProviders = normalizeTrimmedStringList(
|
||||
raw.autoEnableWhenConfiguredProviders,
|
||||
);
|
||||
const name = typeof raw.name === "string" ? raw.name.trim() : undefined;
|
||||
const description = typeof raw.description === "string" ? raw.description.trim() : undefined;
|
||||
const version = typeof raw.version === "string" ? raw.version.trim() : undefined;
|
||||
const channels = normalizeStringList(raw.channels);
|
||||
const providers = normalizeStringList(raw.providers);
|
||||
const channels = normalizeTrimmedStringList(raw.channels);
|
||||
const providers = normalizeTrimmedStringList(raw.providers);
|
||||
const modelSupport = normalizeManifestModelSupport(raw.modelSupport);
|
||||
const cliBackends = normalizeStringList(raw.cliBackends);
|
||||
const cliBackends = normalizeTrimmedStringList(raw.cliBackends);
|
||||
const providerAuthEnvVars = normalizeStringListRecord(raw.providerAuthEnvVars);
|
||||
const channelEnvVars = normalizeStringListRecord(raw.channelEnvVars);
|
||||
const providerAuthChoices = normalizeProviderAuthChoices(raw.providerAuthChoices);
|
||||
const skills = normalizeStringList(raw.skills);
|
||||
const skills = normalizeTrimmedStringList(raw.skills);
|
||||
const contracts = normalizeManifestContracts(raw.contracts);
|
||||
const configContracts = normalizeManifestConfigContracts(raw.configContracts);
|
||||
const channelConfigs = normalizeChannelConfigs(raw.channelConfigs);
|
||||
|
||||
@@ -6,6 +6,25 @@ export function normalizeStringEntriesLower(list?: ReadonlyArray<unknown>) {
|
||||
return normalizeStringEntries(list).map((entry) => entry.toLowerCase());
|
||||
}
|
||||
|
||||
export function normalizeTrimmedStringList(value: unknown): string[] {
|
||||
if (!Array.isArray(value)) {
|
||||
return [];
|
||||
}
|
||||
return value.flatMap((entry) =>
|
||||
typeof entry === "string" && entry.trim() ? [entry.trim()] : [],
|
||||
);
|
||||
}
|
||||
|
||||
export function normalizeSingleOrTrimmedStringList(value: unknown): string[] {
|
||||
if (Array.isArray(value)) {
|
||||
return normalizeTrimmedStringList(value);
|
||||
}
|
||||
if (typeof value === "string" && value.trim()) {
|
||||
return [value.trim()];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export function normalizeHyphenSlug(raw?: string | null) {
|
||||
const trimmed = raw?.trim().toLowerCase() ?? "";
|
||||
if (!trimmed) {
|
||||
|
||||
Reference in New Issue
Block a user