| 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; |
| |
| 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, |
| }; |
| } |
|
|