Files
memind/wechat/verify/generated-thumbnail.mjs
john 28581310da
Memind CI / Test, build, and release guards (pull_request) Successful in 2m45s
feat(wechat): add image generation delivery policy
2026-07-21 23:22:06 +08:00

153 lines
6.0 KiB
JavaScript

import fs from 'node:fs';
import path from 'node:path';
const RASTER_MIME_PATTERN = /^image\/(?:png|jpeg|webp)$/i;
function parseToolResultJson(toolResult) {
const content = toolResult?.value?.content;
if (!Array.isArray(content)) return null;
for (const item of content) {
if (item?.type !== 'text' || !String(item.text ?? '').trim()) continue;
try {
return JSON.parse(String(item.text));
} catch {
// A non-JSON tool message cannot prove a fresh generated image.
}
}
return null;
}
function normalizeGeneratedImage(result) {
const mimeType = String(result?.source?.mimeType ?? result?.asset?.mimeType ?? '').toLowerCase();
if (!result?.ok || !String(result?.jobId ?? '').trim() || !RASTER_MIME_PATTERN.test(mimeType)) {
return null;
}
const asset = result.asset ?? {};
const htmlSrc = String(asset.htmlSrc ?? '').trim();
const publicUrl = String(asset.publicUrl ?? '').trim();
const workspaceRelativePath = String(asset.workspaceRelativePath ?? '').trim();
if (!htmlSrc && !publicUrl && !workspaceRelativePath) return null;
return {
jobId: String(result.jobId),
mimeType,
assetId: String(asset.id ?? '').trim() || null,
htmlSrc: htmlSrc || null,
publicUrl: publicUrl || null,
workspaceRelativePath: workspaceRelativePath || null,
};
}
export function collectWechatGeneratedImages(messages = []) {
const requestNames = new Map();
const images = [];
for (const message of messages) {
for (const item of message?.content ?? []) {
if (item?.type === 'toolRequest') {
const name = String(item?.toolCall?.value?.name ?? '').trim();
if (item.id && name) requestNames.set(String(item.id), name);
continue;
}
if (item?.type !== 'toolResponse') continue;
const name = requestNames.get(String(item.id ?? '')) ?? String(item?.toolResult?.name ?? '');
if (!name.endsWith('generate_image')) continue;
const toolResult = item.toolResult ?? {};
if (toolResult.status !== 'success' || toolResult?.value?.isError === true) continue;
const generated = normalizeGeneratedImage(parseToolResultJson(toolResult));
if (generated && !images.some((image) => image.jobId === generated.jobId)) images.push(generated);
}
}
return images;
}
function decodeHtmlAttribute(value) {
return String(value ?? '')
.replaceAll('"', '"')
.replaceAll('"', '"')
.replaceAll(''', "'")
.replaceAll('&', '&');
}
export function extractMindspaceCoverPath(html) {
const tag = String(html ?? '').match(/<meta[^>]*name=["']mindspace-cover["'][^>]*>/i)?.[0] ?? '';
if (!tag) return '';
const match = tag.match(/content=(['"])([\s\S]*?)\1/i);
if (!match?.[2]) return '';
try {
const meta = JSON.parse(decodeHtmlAttribute(match[2]));
return String(meta.cover ?? meta.image ?? '').trim();
} catch {
return '';
}
}
function normalizeRelativeAssetPath(value) {
const clean = String(value ?? '')
.trim()
.split(/[?#]/, 1)[0]
.replace(/\\/g, '/')
.replace(/^\.\//, '')
.replace(/^\/+/, '');
return clean.startsWith('public/') ? clean.slice('public/'.length) : clean;
}
function imageMatchesCover(image, cover, artifact) {
if (!cover) return false;
if (/^https?:\/\//i.test(cover)) return image.publicUrl === cover;
const coverPath = normalizeRelativeAssetPath(cover);
const artifactRelativePath = normalizeRelativeAssetPath(artifact?.relativePath ?? '');
const artifactDir = path.posix.dirname(artifactRelativePath);
const resolvedCover = normalizeRelativeAssetPath(path.posix.join(artifactDir === '.' ? '' : artifactDir, coverPath));
const candidates = [image.htmlSrc, image.workspaceRelativePath]
.map(normalizeRelativeAssetPath)
.filter(Boolean);
return candidates.includes(coverPath) || candidates.includes(resolvedCover);
}
const AUXILIARY_PAGE_PATH_PATTERN = /(?:^|[-_.\/])(?:admin|manage|management|backend|password|redirect|download|error)(?:[-_.\/]|$)/i;
export function isWechatAuxiliaryPageArtifact(artifact) {
const relativePath = String(artifact?.relativePath ?? '').trim().replace(/\\/g, '/');
if (AUXILIARY_PAGE_PATH_PATTERN.test(relativePath)) return true;
const localPath = String(artifact?.localPath ?? '').trim();
if (!localPath || !fs.existsSync(localPath)) return false;
const html = fs.readFileSync(localPath, 'utf8');
return /<meta[^>]*name=["']mindspace-page-role["'][^>]*content=["'](?:admin|auxiliary)["']/i.test(html)
|| /<[^>]+data-mindspace-page-role=["'](?:admin|auxiliary)["']/i.test(html);
}
export function verifyFreshWechatPageThumbnails(artifacts = [], images = []) {
if (!Array.isArray(artifacts) || artifacts.length === 0) {
return { ok: false, reason: 'missing_page_artifact', matches: [] };
}
const eligibleArtifacts = artifacts.filter((artifact) => !isWechatAuxiliaryPageArtifact(artifact));
const skippedArtifacts = artifacts.filter((artifact) => isWechatAuxiliaryPageArtifact(artifact));
if (eligibleArtifacts.length === 0) {
return { ok: true, reason: null, matches: [], skippedArtifacts };
}
if (!Array.isArray(images) || images.length === 0) {
return { ok: false, reason: 'fresh_image_not_generated', matches: [], skippedArtifacts };
}
const usedJobs = new Set();
const matches = [];
for (const artifact of eligibleArtifacts) {
const localPath = String(artifact?.localPath ?? '').trim();
if (!localPath || !fs.existsSync(localPath)) {
return { ok: false, reason: 'missing_page_artifact', artifact, matches, skippedArtifacts };
}
const cover = extractMindspaceCoverPath(fs.readFileSync(localPath, 'utf8'));
const image = images.find((candidate) => !usedJobs.has(candidate.jobId) && imageMatchesCover(candidate, cover, artifact));
if (!image) {
return {
ok: false,
reason: cover ? 'cover_not_from_current_run' : 'missing_generated_cover',
artifact,
matches,
skippedArtifacts,
};
}
usedJobs.add(image.jobId);
matches.push({ artifact, image, cover });
}
return { ok: true, reason: null, matches, skippedArtifacts };
}