* chore: prepare image-vision skill for template regeneration - Delete stale modify/*.ts templates (built against 1.1.2) - Update core_version to 1.2.6 - Strip fork-specific details from intent docs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(skills): regenerate image-vision modify/ templates against upstream Templates regenerated against upstream 1.2.6: - src/container-runner.ts: imageAttachments field in ContainerInput - src/index.ts: parseImageReferences + threading to runAgent - src/channels/whatsapp.ts: downloadMediaMessage + image handling block - src/channels/whatsapp.test.ts: image mocks + 4 test cases - container/agent-runner/src/index.ts: ContentBlock types, pushMultimodal, image loading Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: update image-vision tests for upstream templates - Relax downloadMediaMessage import pattern check (multi-line import) - Remove check for [Image - processing failed] (not in upstream template) - Add vitest.skills.config.ts for skill package test runs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: update image-vision core_version to 1.2.8 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
64 lines
1.8 KiB
TypeScript
64 lines
1.8 KiB
TypeScript
import fs from 'fs';
|
|
import path from 'path';
|
|
import sharp from 'sharp';
|
|
import type { WAMessage } from '@whiskeysockets/baileys';
|
|
|
|
const MAX_DIMENSION = 1024;
|
|
const IMAGE_REF_PATTERN = /\[Image: (attachments\/[^\]]+)\]/g;
|
|
|
|
export interface ProcessedImage {
|
|
content: string;
|
|
relativePath: string;
|
|
}
|
|
|
|
export interface ImageAttachment {
|
|
relativePath: string;
|
|
mediaType: string;
|
|
}
|
|
|
|
export function isImageMessage(msg: WAMessage): boolean {
|
|
return !!msg.message?.imageMessage;
|
|
}
|
|
|
|
export async function processImage(
|
|
buffer: Buffer,
|
|
groupDir: string,
|
|
caption: string,
|
|
): Promise<ProcessedImage | null> {
|
|
if (!buffer || buffer.length === 0) return null;
|
|
|
|
const resized = await sharp(buffer)
|
|
.resize(MAX_DIMENSION, MAX_DIMENSION, { fit: 'inside', withoutEnlargement: true })
|
|
.jpeg({ quality: 85 })
|
|
.toBuffer();
|
|
|
|
const attachDir = path.join(groupDir, 'attachments');
|
|
fs.mkdirSync(attachDir, { recursive: true });
|
|
|
|
const filename = `img-${Date.now()}-${Math.random().toString(36).slice(2, 6)}.jpg`;
|
|
const filePath = path.join(attachDir, filename);
|
|
fs.writeFileSync(filePath, resized);
|
|
|
|
const relativePath = `attachments/${filename}`;
|
|
const content = caption
|
|
? `[Image: ${relativePath}] ${caption}`
|
|
: `[Image: ${relativePath}]`;
|
|
|
|
return { content, relativePath };
|
|
}
|
|
|
|
export function parseImageReferences(
|
|
messages: Array<{ content: string }>,
|
|
): ImageAttachment[] {
|
|
const refs: ImageAttachment[] = [];
|
|
for (const msg of messages) {
|
|
let match: RegExpExecArray | null;
|
|
IMAGE_REF_PATTERN.lastIndex = 0;
|
|
while ((match = IMAGE_REF_PATTERN.exec(msg.content)) !== null) {
|
|
// Always JPEG — processImage() normalizes all images to .jpg
|
|
refs.push({ relativePath: match[1], mediaType: 'image/jpeg' });
|
|
}
|
|
}
|
|
return refs;
|
|
}
|