Files
Moltbot/extensions/diffs/src/config.ts
2026-03-04 02:35:12 -05:00

404 lines
12 KiB
TypeScript

import type { OpenClawPluginConfigSchema } from "openclaw/plugin-sdk/diffs";
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 DIFFS_PLUGIN_CONFIG_JSON_SCHEMA = {
type: "object",
additionalProperties: false,
properties: {
defaults: {
type: "object",
additionalProperties: false,
properties: {
fontFamily: { type: "string", default: DEFAULT_DIFFS_TOOL_DEFAULTS.fontFamily },
fontSize: {
type: "number",
minimum: 10,
maximum: 24,
default: DEFAULT_DIFFS_TOOL_DEFAULTS.fontSize,
},
lineSpacing: {
type: "number",
minimum: 1,
maximum: 3,
default: DEFAULT_DIFFS_TOOL_DEFAULTS.lineSpacing,
},
layout: {
type: "string",
enum: [...DIFF_LAYOUTS],
default: DEFAULT_DIFFS_TOOL_DEFAULTS.layout,
},
showLineNumbers: {
type: "boolean",
default: DEFAULT_DIFFS_TOOL_DEFAULTS.showLineNumbers,
},
diffIndicators: {
type: "string",
enum: [...DIFF_INDICATORS],
default: DEFAULT_DIFFS_TOOL_DEFAULTS.diffIndicators,
},
wordWrap: { type: "boolean", default: DEFAULT_DIFFS_TOOL_DEFAULTS.wordWrap },
background: { type: "boolean", default: DEFAULT_DIFFS_TOOL_DEFAULTS.background },
theme: {
type: "string",
enum: [...DIFF_THEMES],
default: DEFAULT_DIFFS_TOOL_DEFAULTS.theme,
},
fileFormat: {
type: "string",
enum: [...DIFF_OUTPUT_FORMATS],
default: DEFAULT_DIFFS_TOOL_DEFAULTS.fileFormat,
},
format: {
type: "string",
enum: [...DIFF_OUTPUT_FORMATS],
},
fileQuality: {
type: "string",
enum: [...DIFF_IMAGE_QUALITY_PRESETS],
default: DEFAULT_DIFFS_TOOL_DEFAULTS.fileQuality,
},
fileScale: {
type: "number",
minimum: 1,
maximum: 4,
default: DEFAULT_DIFFS_TOOL_DEFAULTS.fileScale,
},
fileMaxWidth: {
type: "number",
minimum: 640,
maximum: 2400,
default: DEFAULT_DIFFS_TOOL_DEFAULTS.fileMaxWidth,
},
imageFormat: {
type: "string",
enum: [...DIFF_OUTPUT_FORMATS],
},
imageQuality: {
type: "string",
enum: [...DIFF_IMAGE_QUALITY_PRESETS],
},
imageScale: {
type: "number",
minimum: 1,
maximum: 4,
},
imageMaxWidth: {
type: "number",
minimum: 640,
maximum: 2400,
},
mode: {
type: "string",
enum: [...DIFF_MODES],
default: DEFAULT_DIFFS_TOOL_DEFAULTS.mode,
},
},
},
security: {
type: "object",
additionalProperties: false,
properties: {
allowRemoteViewer: {
type: "boolean",
default: DEFAULT_DIFFS_PLUGIN_SECURITY.allowRemoteViewer,
},
},
},
},
} as const;
export const diffsPluginConfigSchema: OpenClawPluginConfigSchema = {
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) }],
},
};
}
},
jsonSchema: DIFFS_PLUGIN_CONFIG_JSON_SCHEMA,
};
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,
};
}