Files
openclaw/extensions/diffs/src/config.ts
2026-03-27 09:55:47 +05:30

355 lines
11 KiB
TypeScript

import { buildPluginConfigSchema } from "openclaw/plugin-sdk/core";
import { z } from "openclaw/plugin-sdk/zod";
import type { OpenClawPluginConfigSchema } from "../api.js";
import {
DIFF_IMAGE_QUALITY_PRESETS,
DIFF_INDICATORS,
DIFF_LAYOUTS,
DIFF_MODES,
DIFF_OUTPUT_FORMATS,
DIFF_THEMES,
type DiffFileDefaults,
type DiffImageQualityPreset,
type DiffIndicators,
type DiffLayout,
type DiffMode,
type DiffOutputFormat,
type DiffPresentationDefaults,
type DiffTheme,
type DiffToolDefaults,
} from "./types.js";
type DiffsPluginConfig = {
defaults?: {
fontFamily?: string;
fontSize?: number;
lineSpacing?: number;
layout?: DiffLayout;
showLineNumbers?: boolean;
diffIndicators?: DiffIndicators;
wordWrap?: boolean;
background?: boolean;
theme?: DiffTheme;
fileFormat?: DiffOutputFormat;
fileQuality?: DiffImageQualityPreset;
fileScale?: number;
fileMaxWidth?: number;
format?: DiffOutputFormat;
// Backward-compatible aliases retained for existing configs.
imageFormat?: DiffOutputFormat;
imageQuality?: DiffImageQualityPreset;
imageScale?: number;
imageMaxWidth?: number;
mode?: DiffMode;
};
security?: {
allowRemoteViewer?: boolean;
};
};
const DEFAULT_IMAGE_QUALITY_PROFILES = {
standard: {
scale: 2,
maxWidth: 960,
maxPixels: 8_000_000,
},
hq: {
scale: 2.5,
maxWidth: 1200,
maxPixels: 14_000_000,
},
print: {
scale: 3,
maxWidth: 1400,
maxPixels: 24_000_000,
},
} as const satisfies Record<
DiffImageQualityPreset,
{ scale: number; maxWidth: number; maxPixels: number }
>;
export const DEFAULT_DIFFS_TOOL_DEFAULTS: DiffToolDefaults = {
fontFamily: "Fira Code",
fontSize: 15,
lineSpacing: 1.6,
layout: "unified",
showLineNumbers: true,
diffIndicators: "bars",
wordWrap: true,
background: true,
theme: "dark",
fileFormat: "png",
fileQuality: "standard",
fileScale: DEFAULT_IMAGE_QUALITY_PROFILES.standard.scale,
fileMaxWidth: DEFAULT_IMAGE_QUALITY_PROFILES.standard.maxWidth,
mode: "both",
};
export type DiffsPluginSecurityConfig = {
allowRemoteViewer: boolean;
};
export const DEFAULT_DIFFS_PLUGIN_SECURITY: DiffsPluginSecurityConfig = {
allowRemoteViewer: false,
};
const DiffsPluginJsonSchemaSource = z.strictObject({
defaults: z
.strictObject({
fontFamily: z.string().default(DEFAULT_DIFFS_TOOL_DEFAULTS.fontFamily).optional(),
fontSize: z.number().min(10).max(24).default(DEFAULT_DIFFS_TOOL_DEFAULTS.fontSize).optional(),
lineSpacing: z
.number()
.min(1)
.max(3)
.default(DEFAULT_DIFFS_TOOL_DEFAULTS.lineSpacing)
.optional(),
layout: z.enum(DIFF_LAYOUTS).default(DEFAULT_DIFFS_TOOL_DEFAULTS.layout).optional(),
showLineNumbers: z.boolean().default(DEFAULT_DIFFS_TOOL_DEFAULTS.showLineNumbers).optional(),
diffIndicators: z
.enum(DIFF_INDICATORS)
.default(DEFAULT_DIFFS_TOOL_DEFAULTS.diffIndicators)
.optional(),
wordWrap: z.boolean().default(DEFAULT_DIFFS_TOOL_DEFAULTS.wordWrap).optional(),
background: z.boolean().default(DEFAULT_DIFFS_TOOL_DEFAULTS.background).optional(),
theme: z.enum(DIFF_THEMES).default(DEFAULT_DIFFS_TOOL_DEFAULTS.theme).optional(),
fileFormat: z
.enum(DIFF_OUTPUT_FORMATS)
.default(DEFAULT_DIFFS_TOOL_DEFAULTS.fileFormat)
.optional(),
format: z.enum(DIFF_OUTPUT_FORMATS).optional(),
fileQuality: z
.enum(DIFF_IMAGE_QUALITY_PRESETS)
.default(DEFAULT_DIFFS_TOOL_DEFAULTS.fileQuality)
.optional(),
fileScale: z.number().min(1).max(4).default(DEFAULT_DIFFS_TOOL_DEFAULTS.fileScale).optional(),
fileMaxWidth: z
.number()
.min(640)
.max(2400)
.default(DEFAULT_DIFFS_TOOL_DEFAULTS.fileMaxWidth)
.optional(),
imageFormat: z.enum(DIFF_OUTPUT_FORMATS).optional(),
imageQuality: z.enum(DIFF_IMAGE_QUALITY_PRESETS).optional(),
imageScale: z.number().min(1).max(4).optional(),
imageMaxWidth: z.number().min(640).max(2400).optional(),
mode: z.enum(DIFF_MODES).default(DEFAULT_DIFFS_TOOL_DEFAULTS.mode).optional(),
})
.optional(),
security: z
.strictObject({
allowRemoteViewer: z
.boolean()
.default(DEFAULT_DIFFS_PLUGIN_SECURITY.allowRemoteViewer)
.optional(),
})
.optional(),
});
export const diffsPluginConfigSchema: OpenClawPluginConfigSchema = buildPluginConfigSchema(
DiffsPluginJsonSchemaSource,
{
safeParse(value: unknown) {
if (value === undefined) {
return { success: true, data: undefined };
}
try {
return { success: true, data: resolveDiffsPluginDefaults(value) };
} catch (error) {
return {
success: false,
error: {
issues: [{ path: [], message: error instanceof Error ? error.message : String(error) }],
},
};
}
},
},
);
export function resolveDiffsPluginDefaults(config: unknown): DiffToolDefaults {
if (!config || typeof config !== "object" || Array.isArray(config)) {
return { ...DEFAULT_DIFFS_TOOL_DEFAULTS };
}
const defaults = (config as DiffsPluginConfig).defaults;
if (!defaults || typeof defaults !== "object" || Array.isArray(defaults)) {
return { ...DEFAULT_DIFFS_TOOL_DEFAULTS };
}
const fileQuality = normalizeFileQuality(defaults.fileQuality ?? defaults.imageQuality);
const profile = DEFAULT_IMAGE_QUALITY_PROFILES[fileQuality];
return {
fontFamily: normalizeFontFamily(defaults.fontFamily),
fontSize: normalizeFontSize(defaults.fontSize),
lineSpacing: normalizeLineSpacing(defaults.lineSpacing),
layout: normalizeLayout(defaults.layout),
showLineNumbers: defaults.showLineNumbers !== false,
diffIndicators: normalizeDiffIndicators(defaults.diffIndicators),
wordWrap: defaults.wordWrap !== false,
background: defaults.background !== false,
theme: normalizeTheme(defaults.theme),
fileFormat: normalizeFileFormat(defaults.fileFormat ?? defaults.imageFormat ?? defaults.format),
fileQuality,
fileScale: normalizeFileScale(defaults.fileScale ?? defaults.imageScale, profile.scale),
fileMaxWidth: normalizeFileMaxWidth(
defaults.fileMaxWidth ?? defaults.imageMaxWidth,
profile.maxWidth,
),
mode: normalizeMode(defaults.mode),
};
}
export function resolveDiffsPluginSecurity(config: unknown): DiffsPluginSecurityConfig {
if (!config || typeof config !== "object" || Array.isArray(config)) {
return { ...DEFAULT_DIFFS_PLUGIN_SECURITY };
}
const security = (config as DiffsPluginConfig).security;
if (!security || typeof security !== "object" || Array.isArray(security)) {
return { ...DEFAULT_DIFFS_PLUGIN_SECURITY };
}
return {
allowRemoteViewer: security.allowRemoteViewer === true,
};
}
export function toPresentationDefaults(defaults: DiffToolDefaults): DiffPresentationDefaults {
const {
fontFamily,
fontSize,
lineSpacing,
layout,
showLineNumbers,
diffIndicators,
wordWrap,
background,
theme,
} = defaults;
return {
fontFamily,
fontSize,
lineSpacing,
layout,
showLineNumbers,
diffIndicators,
wordWrap,
background,
theme,
};
}
function normalizeFontFamily(fontFamily?: string): string {
const normalized = fontFamily?.trim();
return normalized || DEFAULT_DIFFS_TOOL_DEFAULTS.fontFamily;
}
function normalizeFontSize(fontSize?: number): number {
if (fontSize === undefined || !Number.isFinite(fontSize)) {
return DEFAULT_DIFFS_TOOL_DEFAULTS.fontSize;
}
const rounded = Math.floor(fontSize);
return Math.min(Math.max(rounded, 10), 24);
}
function normalizeLineSpacing(lineSpacing?: number): number {
if (lineSpacing === undefined || !Number.isFinite(lineSpacing)) {
return DEFAULT_DIFFS_TOOL_DEFAULTS.lineSpacing;
}
return Math.min(Math.max(lineSpacing, 1), 3);
}
function normalizeLayout(layout?: DiffLayout): DiffLayout {
return layout && DIFF_LAYOUTS.includes(layout) ? layout : DEFAULT_DIFFS_TOOL_DEFAULTS.layout;
}
function normalizeDiffIndicators(diffIndicators?: DiffIndicators): DiffIndicators {
return diffIndicators && DIFF_INDICATORS.includes(diffIndicators)
? diffIndicators
: DEFAULT_DIFFS_TOOL_DEFAULTS.diffIndicators;
}
function normalizeTheme(theme?: DiffTheme): DiffTheme {
return theme && DIFF_THEMES.includes(theme) ? theme : DEFAULT_DIFFS_TOOL_DEFAULTS.theme;
}
function normalizeFileFormat(fileFormat?: DiffOutputFormat): DiffOutputFormat {
return fileFormat && DIFF_OUTPUT_FORMATS.includes(fileFormat)
? fileFormat
: DEFAULT_DIFFS_TOOL_DEFAULTS.fileFormat;
}
function normalizeFileQuality(fileQuality?: DiffImageQualityPreset): DiffImageQualityPreset {
return fileQuality && DIFF_IMAGE_QUALITY_PRESETS.includes(fileQuality)
? fileQuality
: DEFAULT_DIFFS_TOOL_DEFAULTS.fileQuality;
}
function normalizeFileScale(fileScale: number | undefined, fallback: number): number {
if (fileScale === undefined || !Number.isFinite(fileScale)) {
return fallback;
}
const rounded = Math.round(fileScale * 100) / 100;
return Math.min(Math.max(rounded, 1), 4);
}
function normalizeFileMaxWidth(fileMaxWidth: number | undefined, fallback: number): number {
if (fileMaxWidth === undefined || !Number.isFinite(fileMaxWidth)) {
return fallback;
}
const rounded = Math.round(fileMaxWidth);
return Math.min(Math.max(rounded, 640), 2400);
}
function normalizeMode(mode?: DiffMode): DiffMode {
return mode && DIFF_MODES.includes(mode) ? mode : DEFAULT_DIFFS_TOOL_DEFAULTS.mode;
}
export function resolveDiffImageRenderOptions(params: {
defaults: DiffFileDefaults;
fileFormat?: DiffOutputFormat;
format?: DiffOutputFormat;
fileQuality?: DiffImageQualityPreset;
fileScale?: number;
fileMaxWidth?: number;
imageFormat?: DiffOutputFormat;
imageQuality?: DiffImageQualityPreset;
imageScale?: number;
imageMaxWidth?: number;
}): {
format: DiffOutputFormat;
qualityPreset: DiffImageQualityPreset;
scale: number;
maxWidth: number;
maxPixels: number;
} {
const format = normalizeFileFormat(
params.fileFormat ?? params.imageFormat ?? params.format ?? params.defaults.fileFormat,
);
const qualityOverrideProvided =
params.fileQuality !== undefined || params.imageQuality !== undefined;
const qualityPreset = normalizeFileQuality(
params.fileQuality ?? params.imageQuality ?? params.defaults.fileQuality,
);
const profile = DEFAULT_IMAGE_QUALITY_PROFILES[qualityPreset];
const scale = normalizeFileScale(
params.fileScale ?? params.imageScale,
qualityOverrideProvided ? profile.scale : params.defaults.fileScale,
);
const maxWidth = normalizeFileMaxWidth(
params.fileMaxWidth ?? params.imageMaxWidth,
qualityOverrideProvided ? profile.maxWidth : params.defaults.fileMaxWidth,
);
return {
format,
qualityPreset,
scale,
maxWidth,
maxPixels: profile.maxPixels,
};
}