"use strict"; import { readFileSync, statSync, existsSync } from "node:fs"; import { resolve, extname } from "node:path"; /** 支持的图片格式及其 MIME 类型 */ const IMAGE_MIME_TYPES: Record = { png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", }; /** 支持的视频格式及其 MIME 类型 */ const VIDEO_MIME_TYPES: Record = { mp4: "video/mp4", mov: "video/quicktime", m4v: "video/x-m4v", avi: "video/x-msvideo", webm: "video/webm", wmv: "video/x-ms-wmv", }; export interface ImageUrlPart { type: "image_url"; image_url: { url: string }; } export interface VideoUrlPart { type: "video_url"; video_url: { url: string }; } export interface TextPart { type: "text"; text: string; } export type ContentPart = ImageUrlPart | VideoUrlPart | TextPart; /** 判断是否为 HTTP(S) URL */ export function isUrl(source: string): boolean { return source.startsWith("http://") || source.startsWith("https://"); } /** 剥离 pi 的 @ 路径前缀 */ function stripAtPrefix(path: string): string { return path.startsWith("@") ? path.slice(1) : path; } /** 解析文件路径(相对 cwd + 去掉 @ 前缀) */ function resolvePath(rawPath: string, cwd: string): string { const cleaned = stripAtPrefix(rawPath); return resolve(cwd, cleaned); } /** * 处理图片源(本地文件或 URL),返回 API 所需的 ContentPart。 * 本地文件会 base64 编码为 data URL。 */ export function processImage( source: string, cwd: string, maxSizeMB: number ): ImageUrlPart { if (isUrl(source)) { return { type: "image_url", image_url: { url: source } }; } const resolved = resolvePath(source, cwd); if (!existsSync(resolved)) { throw new Error(`Image file not found: ${resolved}`); } const stat = statSync(resolved); const sizeMB = stat.size / (1024 * 1024); if (sizeMB > maxSizeMB) { throw new Error( `Image file too large: ${sizeMB.toFixed(1)}MB (max ${maxSizeMB}MB): ${resolved}` ); } const ext = extname(resolved).slice(1).toLowerCase(); const mime = IMAGE_MIME_TYPES[ext]; if (!mime) { throw new Error( `Unsupported image format: .${ext}. Supported: ${Object.keys(IMAGE_MIME_TYPES).join(", ")}` ); } const data = readFileSync(resolved); const b64 = data.toString("base64"); return { type: "image_url", image_url: { url: `data:${mime};base64,${b64}` }, }; } /** * 处理视频源(本地文件或 URL),返回 API 所需的 ContentPart。 * 本地文件会 base64 编码为 data URL。 */ export function processVideo( source: string, cwd: string, maxSizeMB: number ): VideoUrlPart { if (isUrl(source)) { return { type: "video_url", video_url: { url: source } }; } const resolved = resolvePath(source, cwd); if (!existsSync(resolved)) { throw new Error(`Video file not found: ${resolved}`); } const stat = statSync(resolved); const sizeMB = stat.size / (1024 * 1024); if (sizeMB > maxSizeMB) { throw new Error( `Video file too large: ${sizeMB.toFixed(1)}MB (max ${maxSizeMB}MB): ${resolved}` ); } const ext = extname(resolved).slice(1).toLowerCase(); const mime = VIDEO_MIME_TYPES[ext]; if (!mime) { throw new Error( `Unsupported video format: .${ext}. Supported: ${Object.keys(VIDEO_MIME_TYPES).join(", ")}` ); } const data = readFileSync(resolved); const b64 = data.toString("base64"); return { type: "video_url", video_url: { url: `data:${mime};base64,${b64}` }, }; }