Project Files
src / helpers / visionPromotionLog.ts
import fs from "fs";
import path from "path";
import { ensureLogsDir, getLogsDir } from "../core-bundle.mjs";
export type VisionPromotionMode = "idempotent" | "persistent";
export type VisionPromotionTurnInjectedAttachment = {
/** Alias for n (Attachment [aN]). Kept for naming hygiene; n stays for backward compat. */
a?: number;
n?: number;
filename: string;
origin?: string;
originAbs?: string;
originalName?: string;
preview?: string;
previewAbs?: string;
};
export type VisionPromotionTurnInjectedVariant = {
filename: string;
originAbs?: string;
sourceTool?: string;
sourceUrl?: string;
preview?: string;
previewAbs?: string;
v?: number;
};
export type VisionPromotionTurnInjectedPicture = {
filename: string;
originAbs?: string;
sourceTool?: string;
sourceUrl?: string;
preview?: string;
previewAbs?: string;
p?: number;
};
export type VisionPromotionTurnInjectedImage = {
filename: string;
originAbs?: string;
/**
* Provenance: "pluginId, toolName" format.
* Example: "mcp/generate-image, generate_image"
*/
sourceTool?: string;
preview?: string;
previewAbs?: string;
i?: number;
};
export type VisionPromotionJsonlEntry =
| {
type: "turn";
chatId: string;
requestId: string;
promotedAt: string;
mode: VisionPromotionMode;
shouldInjectPixels: boolean;
triggers: {
visionPromotionPersistent: boolean;
/** True when pixel promotion is forced (e.g., abort recovery) for this turn. */
forceVip: boolean;
/** True when a force flag existed in state at turn start (even if stale). */
forceVipRequested?: boolean;
/** Optional reason for forced promotion (e.g., "abort"). */
forceVipReason?: string;
/** True when a stale force flag was cleared due to TTL expiry. */
forceVipStaleCleared?: boolean;
/** True when a force flag was consumed after successful pixel injection. */
forceVipConsumed?: boolean;
/** True when one-shot review promotion is active for this turn. */
reviewVip?: boolean;
/** True when a review request existed in state at turn start (even if stale). */
reviewVipRequested?: boolean;
/** Optional reason recorded by review_image(). */
reviewVipReason?: string;
/** True when a stale review request was cleared due to TTL expiry. */
reviewVipStaleCleared?: boolean;
/** True when a review request was consumed (cleared) this turn. */
reviewVipConsumed?: boolean;
shouldPromoteAttachment: boolean;
shouldPromoteVariants: boolean;
shouldPromoteImages: boolean;
};
counts: {
attachmentLabels: number;
variantLabels: number;
imageLabels: number;
itemsRequested: number;
itemsKept: number;
itemsDroppedMissingPreview: number;
injectedAttachments: number;
injectedPictures: number;
injectedVariants: number;
injectedImages: number;
};
injected: {
attachments: VisionPromotionTurnInjectedAttachment[];
pictures: VisionPromotionTurnInjectedPicture[];
variants: VisionPromotionTurnInjectedVariant[];
images: VisionPromotionTurnInjectedImage[];
};
/** Outcome verification: measured on consolidated messages, after all transformations. */
outcome?: {
/** Actual count of image_url parts in the final request. */
imageUrlParts: number;
/** image_url parts that existed before VisionPromotion ran (tool results, user attachments). */
preExistingImageUrlParts?: number;
/** Sequence review frames injected (excluded from VP count). */
seqFramesInjected?: number;
/** image_url parts attributable to VisionPromotion only. */
imageUrlPartsVpOnly?: number;
/** True iff imageUrlPartsVpOnly === itemsKept (strict count matching). */
verified: boolean;
/** Error type if verification failed. */
error: "pixels_missing" | "pixels_excess" | null;
};
}
| {
type: "attachment";
filename: string;
origin?: string; // LM Studio fileIdentifier
originAbs?: string;
originalName?: string; // real original filename from metadata (e.g., "Katze.png")
preview?: string;
previewAbs?: string;
createdAt: string;
/** Alias for n (Attachment [aN]). Kept for naming hygiene; n stays for backward compat. */
a?: number;
n?: number;
chatId: string;
requestId: string;
promotedAt: string;
mode: VisionPromotionMode;
pixelPromoted: boolean; // true iff this attachment's preview bytes were injected this turn
}
| {
type: "variant";
filename: string;
originAbs?: string;
preview: string;
previewAbs: string;
createdAt: string;
v?: number;
sourceTool?: string;
sourceUrl?: string;
chatId: string;
requestId: string;
promotedAt: string;
mode: VisionPromotionMode;
pixelPromoted: boolean; // true iff this variant's preview bytes were injected this turn
}
| {
type: "picture";
filename: string;
originAbs?: string;
preview: string;
previewAbs: string;
createdAt: string;
p?: number;
sourceTool?: string;
sourceUrl?: string;
chatId: string;
requestId: string;
promotedAt: string;
mode: VisionPromotionMode;
pixelPromoted: boolean; // true iff this picture's preview bytes were injected this turn
}
| {
type: "image";
filename: string;
originAbs?: string;
preview: string;
previewAbs: string;
createdAt: string;
i?: number;
/**
* Provenance: "pluginId, toolName" format.
* Example: "mcp/generate-image, generate_image"
*/
sourceTool?: string;
chatId: string;
requestId: string;
promotedAt: string;
mode: VisionPromotionMode;
pixelPromoted: boolean; // true iff this image's preview bytes were injected this turn
}
| {
type: "error";
chatId: string;
requestId: string;
promotedAt: string;
mode: VisionPromotionMode;
stage: string;
message: string;
}
| {
type: "outcome";
chatId: string;
requestId: string;
promotedAt: string;
mode: VisionPromotionMode;
itemsKept: number;
/** Number of sequence-review frames injected in this turn (0 when absent). */
seqFramesInjected?: number;
outcome: {
imageUrlParts: number;
preExistingImageUrlParts?: number;
seqFramesInjected?: number;
imageUrlPartsVpOnly?: number;
verified: boolean;
error: "pixels_missing" | "pixels_excess" | null;
};
};
export function appendVisionPromotionJsonl(
entries: VisionPromotionJsonlEntry[]
): void {
if (!entries.length) return;
try {
ensureLogsDir();
const logsDir = getLogsDir();
const logFile = path.join(logsDir, "vision-promotion.jsonl");
// Human-readable, append-only: write each entry as a pretty-printed JSON block.
// NOTE: This is intentionally not strict 1-line-per-entry JSONL/NDJSON.
const blocks = entries
.map((e) => `${JSON.stringify(e, null, 2)}\n\n`)
.join("");
fs.appendFileSync(logFile, blocks);
} catch (e) {
// eslint-disable-next-line no-console
console.error(
"[VisionPromotion] Failed to append to vision-promotion.jsonl:",
e instanceof Error ? e.message : String(e)
);
}
}
export function parseVisionPromotionJsonlText(text: string): {
entries: VisionPromotionJsonlEntry[];
errors: Array<{ error: string; blockPreview: string }>;
} {
const entries: VisionPromotionJsonlEntry[] = [];
const errors: Array<{ error: string; blockPreview: string }> = [];
// Split on blank-line boundaries while keeping pretty-printed JSON blocks intact.
const blocks = text
.split(/\n\s*\n+/g)
.map((b) => b.trim())
.filter(Boolean);
for (const block of blocks) {
try {
const obj = JSON.parse(block);
entries.push(obj as VisionPromotionJsonlEntry);
} catch (e) {
errors.push({
error: e instanceof Error ? e.message : String(e),
blockPreview: block.slice(0, 200),
});
}
}
return { entries, errors };
}
export async function readVisionPromotionJsonlEntries(): Promise<{
filePath: string;
entries: VisionPromotionJsonlEntry[];
errors: Array<{ error: string; blockPreview: string }>;
}> {
const logsDir = getLogsDir();
const filePath = path.join(logsDir, "vision-promotion.jsonl");
const text = await fs.promises.readFile(filePath, "utf8");
const parsed = parseVisionPromotionJsonlText(text);
return { filePath, ...parsed };
}