Project Files
src / visionModeSelector.ts
import { type BuildVisionPromotionPartsParams } from "./promotion";
import { buildPromotionPartsA } from "./noPromotion";
import { buildPromotionPartsB } from "./promotionBase64";
import { buildPromotionPartsFiles } from "./promotionFiles";
import { cleanupChatFiles } from "./files-api";
export async function buildPromotionPartsForMode(
params: BuildVisionPromotionPartsParams & { useFilesApiForVision: boolean; suppressVisionPromotionForThisTurn?: boolean; visionPromotionPersistent?: boolean }
): Promise<{ promoParts: any[]; promotedFiles: string[] }> {
const { useFilesApiForVision, suppressVisionPromotionForThisTurn, visionPromotionPersistent, ...rest } = params as any;
// Hard override: if this turn is marked as unsafe for promotion
// (e.g. tool/functionResponse replay with thought_signature), skip images entirely.
if (suppressVisionPromotionForThisTurn) {
return buildPromotionPartsA(rest);
}
// Vision Promotion is always ON - two modes:
// - Persistent (visionPromotionPersistent=true): re-inject every turn
// - Idempotent (visionPromotionPersistent=false): inject only when new
// The idempotency logic is handled inside the promotion functions via shouldPromoteImages()
if (useFilesApiForVision) {
return buildPromotionPartsFiles({ ...rest, visionPromotionPersistent });
}
// Switched to Base64 mode - cleanup any Files API uploads for this chat
// This prevents orphaned files when user toggles the setting
if (rest.apiKey && rest.chatWd) {
try {
await cleanupChatFiles(rest.apiKey, rest.chatWd);
} catch (e) {
console.warn("[VisionModeSelector] Failed to cleanup Files API on mode switch:", (e as Error).message);
}
}
// Fallback to Base64 mode (Mode B)
return buildPromotionPartsB({ ...rest, visionPromotionPersistent });
}