import { $env, $flag } from "@oh-my-pi/pi-utils"; const SIXEL_START_REGEX = /\x1bP(?:[0-9;]*)q/u; const SIXEL_END_SEQUENCE = "\x1b\\"; const SIXEL_END_BELL = "\x07"; const SIXEL_SEQUENCE_REGEX = /\x1bP(?:[0-9;]*)q[\s\S]*?(?:\x1b\\|\x07)/gu; const SIXEL_PLACEHOLDER_PREFIX = "__OMP_SIXEL_SEQUENCE_"; /** * Returns whether SIXEL passthrough is explicitly enabled. * * Both gates must be enabled to preserve SIXEL control sequences: * - PI_FORCE_IMAGE_PROTOCOL=sixel * - PI_ALLOW_SIXEL_PASSTHROUGH=1 */ export function isSixelPassthroughEnabled(): boolean { const forcedProtocol = $env.PI_FORCE_IMAGE_PROTOCOL?.trim().toLowerCase(); return forcedProtocol === "sixel" && $flag("PI_ALLOW_SIXEL_PASSTHROUGH"); } /** Returns true when the text contains a SIXEL start sequence. */ export function containsSixelSequence(text: string): boolean { return SIXEL_START_REGEX.test(text); } /** * Returns a boolean mask indicating which lines belong to a SIXEL sequence block. * Supports multi-line SIXEL payloads generated by libsixel. */ export function getSixelLineMask(lines: string[]): boolean[] { let inSequence = false; return lines.map(line => { const hasStart = containsSixelSequence(line); if (hasStart) { inSequence = true; } const isSixelLine = inSequence; if (inSequence && (line.includes(SIXEL_END_SEQUENCE) || line.includes(SIXEL_END_BELL))) { inSequence = false; } return isSixelLine; }); } /** Returns true when the line contains a SIXEL start sequence. */ export function isSixelLine(line: string): boolean { return containsSixelSequence(line); } /** * Sanitizes text while preserving embedded SIXEL sequences when passthrough is enabled. */ export function sanitizeWithOptionalSixelPassthrough(text: string, sanitize: (text: string) => string): string { if (!isSixelPassthroughEnabled() || !containsSixelSequence(text)) { return sanitize(text); } const preservedSequences: string[] = []; const tokenized = text.replace(SIXEL_SEQUENCE_REGEX, match => { const token = `${SIXEL_PLACEHOLDER_PREFIX}${preservedSequences.length}__`; preservedSequences.push(match); return token; }); const sanitized = sanitize(tokenized); return sanitized.replace(/__OMP_SIXEL_SEQUENCE_(\d+)__/gu, (_, indexText: string) => { const index = Number.parseInt(indexText, 10); return preservedSequences[index] ?? ""; }); }