export type Vip98kTaskInvocation = { kind: string; method: string; path: string; contentType: string; headers?: Record; requestExample: Record; inputSchema?: Record | null; resultMethod: string; resultPath: string; }; type KnownImageInput = "images" | "video_clips"; const VIP98K_IMAGE_SIZE_LONGEST_EDGE: Record = { "1K": 1024, "2K": 2048, "4K": 4096, }; const GPT_IMAGE_25_TARGET_PIXELS: Record = { "1K": 1024 * 1024, "2K": 2048 * 2048, "4K": 2880 * 2880, }; function normalizeVip98kPixelSize(value: unknown) { const match = String(value || "") .trim() .match(/^(\d+)\s*[x*×]\s*(\d+)$/i); if (!match?.[1] || !match[2]) return null; const sourceWidth = Number(match[1]); const sourceHeight = Number(match[2]); if ( !Number.isFinite(sourceWidth) || !Number.isFinite(sourceHeight) || sourceWidth < 1 || sourceHeight < 1 ) { return null; } const scale = Math.min(1, 4096 / Math.max(sourceWidth, sourceHeight)); return `${Math.max(1, Math.round(sourceWidth * scale))}x${Math.max( 1, Math.round(sourceHeight * scale), )}`; } export function resolveVip98kImageTaskSize(params: { imageSize?: unknown; aspectRatio?: unknown; fallbackSize?: unknown; preserveResolutionBucket?: boolean; useGptImage25AspectSizing?: boolean; }) { const explicitSize = normalizeVip98kPixelSize(params.imageSize); if (explicitSize) return explicitSize; const ratioMatch = String(params.aspectRatio || "") .trim() .match(/^(\d+(?:\.\d+)?)\s*:\s*(\d+(?:\.\d+)?)$/); const sizeBucket = String(params.imageSize || "").trim().toUpperCase(); const longestEdge = VIP98K_IMAGE_SIZE_LONGEST_EDGE[sizeBucket]; if (params.useGptImage25AspectSizing && sizeBucket in GPT_IMAGE_25_TARGET_PIXELS && (!ratioMatch || Number(ratioMatch[1]) === Number(ratioMatch[2]))) { const edge = Math.sqrt(GPT_IMAGE_25_TARGET_PIXELS[sizeBucket]); return `${edge}x${edge}`; } if ( params.useGptImage25AspectSizing && ratioMatch?.[1] && ratioMatch[2] && sizeBucket in GPT_IMAGE_25_TARGET_PIXELS ) { const ratioWidth = Number(ratioMatch[1]); const ratioHeight = Number(ratioMatch[2]); if (ratioWidth > 0 && ratioHeight > 0 && ratioWidth !== ratioHeight) { const ratio = ratioWidth / ratioHeight; const targetPixels = GPT_IMAGE_25_TARGET_PIXELS[sizeBucket] || 0; let width = Math.sqrt(targetPixels * ratio); let height = Math.sqrt(targetPixels / ratio); const maxEdgeScale = Math.min(1, 3840 / Math.max(width, height)); width *= maxEdgeScale; height *= maxEdgeScale; const clampDimension = (value: number) => Math.max(16, Math.min(3840, value)); const roundToSixteen = (value: number) => clampDimension(Math.round(value / 16) * 16); const floorToSixteen = (value: number) => clampDimension(Math.floor(value / 16) * 16); const shouldFloorWidth = sizeBucket !== "4K" && width < height; const shouldFloorHeight = sizeBucket !== "4K" && height < width; let outputWidth = shouldFloorWidth ? floorToSixteen(width) : roundToSixteen(width); let outputHeight = shouldFloorHeight ? floorToSixteen(height) : roundToSixteen(height); // Flare promotes even a small area overflow to the next tier (2736x1536 -> 4K). if (sizeBucket !== "4K" && outputWidth * outputHeight > targetPixels) { if (outputWidth >= outputHeight) outputWidth -= 16; else outputHeight -= 16; } return `${outputWidth}x${outputHeight}`; } } if (longestEdge && params.preserveResolutionBucket) return sizeBucket; if (ratioMatch?.[1] && ratioMatch[2] && longestEdge) { const ratioWidth = Number(ratioMatch[1]); const ratioHeight = Number(ratioMatch[2]); if (ratioWidth > 0 && ratioHeight > 0) { const scale = longestEdge / Math.max(ratioWidth, ratioHeight); const roundToEight = (value: number) => Math.max(8, Math.min(4096, Math.round(value / 8) * 8)); return `${roundToEight(ratioWidth * scale)}x${roundToEight( ratioHeight * scale, )}`; } } const fallbackPixelSize = normalizeVip98kPixelSize(params.fallbackSize); if (fallbackPixelSize) return fallbackPixelSize; return String(params.fallbackSize || "").trim().toLowerCase() === "auto" ? "auto" : undefined; } const IMAGE_ARRAY_INPUT_KEYS = [ "file_urls", "images", "imageUrls", "referenceImages", "reference_images", ] as const; const IMAGE_SINGLE_INPUT_KEYS = [ "image", "imageUrl", "image_url", "firstFrameImage", "first_frame_image", "firstFrameImageUrl", "first_frame_image_url", ] as const; const KNOWN_IMAGE_TOOL_INPUTS: Record = { "google/nano-banana-2/reference-to-image": "video_clips", "google/nano-banana-pro/edit": "images", "openai/gpt-image-2/edit": "images", }; const VERIFIED_OPENAI_IMAGE_ARRAY_MODEL_IDS = new Set([ "gpt-image-2-vip", "gpt-image-2.5", "gpt-image-2.5-flare", ]); const VERIFIED_GPT_IMAGE_25_RESOLUTION_MODEL_IDS = new Set([ // Paid two-reference probes on 2026-09-11; 4K square is 2880px, not 4096px. "gpt-image-2-vip", "gpt-image-2.5-flare", ]); export function hasVerifiedVip98kOpenAiImageArrayContract(modelId: string) { return VERIFIED_OPENAI_IMAGE_ARRAY_MODEL_IDS.has( modelId.trim().toLowerCase(), ); } export function hasVerifiedVip98kGptImage25ResolutionTiers(modelId: string) { return VERIFIED_GPT_IMAGE_25_RESOLUTION_MODEL_IDS.has( modelId.trim().toLowerCase(), ); } const KNOWN_IMAGE_PROCESSING_TOOL_REQUESTS: Record< string, Record > = { "remove-background": { image_url: "" }, enhance: { image_url: "", scale_factor: 4 }, }; export function normalizeVip98kInvocationHeaders(value: unknown) { if (!value || typeof value !== "object" || Array.isArray(value)) return {}; return Object.fromEntries( Object.entries(value as Record).flatMap(([key, headerValue]) => { const normalizedKey = key.trim(); if (!/^x-relay-[a-z0-9-]+$/i.test(normalizedKey)) return []; if ( typeof headerValue !== "string" && typeof headerValue !== "number" && typeof headerValue !== "boolean" ) { return []; } const normalizedValue = String(headerValue).trim(); return normalizedValue ? [[normalizedKey, normalizedValue]] : []; }), ); } function getInvocationToolId(invocation: Vip98kTaskInvocation | null) { if (!invocation || invocation.kind !== "tool") return null; const query = invocation.path.split("?", 2)[1] || ""; return new URLSearchParams(query).get("tool_id"); } function invocationTargetsModel( invocation: Vip98kTaskInvocation | null, modelId: string, ) { if (!invocation) return false; const expected = modelId.trim().toLowerCase(); if (!expected) return false; const requestModel = String(invocation.requestExample.model || "") .trim() .toLowerCase(); if (requestModel) return requestModel === expected; return getInvocationToolId(invocation)?.toLowerCase() === expected; } export function getVip98kImageBindingKeys(invocation: Vip98kTaskInvocation | null) { if (!invocation) return []; const body = invocation.requestExample || {}; const properties = (invocation.inputSchema?.properties || {}) as Record; return [ ...IMAGE_ARRAY_INPUT_KEYS.filter((key) => key in body || key in properties), ...IMAGE_SINGLE_INPUT_KEYS.filter((key) => key in body || key in properties), ...(Array.isArray(body.video_clips) ? ["video_clips" as const] : []), ]; } export function invocationSupportsVip98kImages( invocation: Vip98kTaskInvocation | null, ) { return getVip98kImageBindingKeys(invocation).length > 0; } export function bindVip98kImages( body: Record, imageUrls: string[], ) { const bindingKeys: string[] = []; if (imageUrls.length === 0) return bindingKeys; for (const key of IMAGE_ARRAY_INPUT_KEYS) { if (!(key in body)) continue; body[key] = imageUrls; bindingKeys.push(key); } for (const key of IMAGE_SINGLE_INPUT_KEYS) { if (!(key in body)) continue; body[key] = Array.isArray(body[key]) ? imageUrls : imageUrls[0]; bindingKeys.push(key); } if (Array.isArray(body.video_clips)) { const first = body.video_clips[0]; const template = first && typeof first === "object" && !Array.isArray(first) ? (first as Record) : {}; body.video_clips = imageUrls.map((url) => ({ ...template, url })); bindingKeys.push("video_clips"); } const secondImageBindings = [ "lastFrameImage", "last_frame_image", "endImageUrl", "end_image_url", ] as const; if (imageUrls[1]) { for (const key of secondImageBindings) { if (!(key in body)) continue; body[key] = imageUrls[1]; bindingKeys.push(key); } } return bindingKeys; } function scoreReferenceInvocation(invocation: Vip98kTaskInvocation) { const body = invocation.requestExample || {}; let score = 0; if ("file_urls" in body) score += 60; if (IMAGE_ARRAY_INPUT_KEYS.some((key) => key in body)) score += 40; if (IMAGE_SINGLE_INPUT_KEYS.some((key) => Array.isArray(body[key]))) score += 40; if (Array.isArray(body.video_clips)) score += 35; if (IMAGE_SINGLE_INPUT_KEYS.some((key) => key in body)) score += 15; if (invocation.kind === "task") score += 10; if (invocation.path === "/extensions/v1/tasks") score += 10; return score; } export function selectVip98kTaskInvocation(params: { modelId: string; configuredInvocation: Vip98kTaskInvocation | null; configuredInvocations?: Vip98kTaskInvocation[]; imageCount?: number; }) { const candidates = [ ...(params.configuredInvocations || []), ...(params.configuredInvocation ? [params.configuredInvocation] : []), ].filter( (candidate, index, items) => candidate.path.startsWith("/") && items.findIndex( (item) => item.method === candidate.method && item.path === candidate.path && JSON.stringify(item.requestExample) === JSON.stringify(candidate.requestExample) && JSON.stringify(item.inputSchema || null) === JSON.stringify(candidate.inputSchema || null), ) === index, ); if ((params.imageCount || 0) > 0) { const referenceInvocation = candidates .filter(invocationSupportsVip98kImages) .sort((a, b) => scoreReferenceInvocation(b) - scoreReferenceInvocation(a))[0]; if (referenceInvocation) { return resolveVip98kTaskInvocation(params.modelId, referenceInvocation); } } return resolveVip98kTaskInvocation( params.modelId, params.configuredInvocation || candidates[0] || null, ); } function createKnownImageToolInvocation( modelId: string, imageInput: KnownImageInput ): Vip98kTaskInvocation { const encodedModelId = encodeURIComponent(modelId); const requestExample = imageInput === "video_clips" ? { prompt: "", video_clips: [{ fps: 1, url: "", ends: 0, start: 0 }], } : { prompt: "", images: [""] }; return { kind: "tool", method: "POST", path: `/api/external/tools/generate?tool_id=${encodedModelId}`, contentType: "application/json", requestExample, resultMethod: "GET", resultPath: `/api/external/tools/result?tool_id=${encodedModelId}&taskId={task_id}`, }; } function createKnownProcessingToolInvocation( modelId: string, requestExample: Record ): Vip98kTaskInvocation { const encodedModelId = encodeURIComponent(modelId); return { kind: "tool", method: "POST", path: `/api/external/tools/generate?tool_id=${encodedModelId}`, contentType: "application/json", requestExample: { ...requestExample }, resultMethod: "GET", resultPath: `/api/external/tools/result?tool_id=${encodedModelId}&taskId={task_id}`, }; } function createVerifiedOpenAiImageInvocation(modelId: string) { return { kind: "tool", method: "POST", path: "/v1/images/generations", contentType: "application/json", requestExample: { image: [], model: modelId, n: 1, prompt: "", quality: "auto", size: "1024x1024", stream: false, }, inputSchema: { properties: { aspect_ratio: { type: "string" }, }, type: "object", }, resultMethod: "GET", resultPath: "", } satisfies Vip98kTaskInvocation; } function addVerifiedOpenAiImageFields( invocation: Vip98kTaskInvocation, modelId: string, ): Vip98kTaskInvocation { const inputSchema = invocation.inputSchema || {}; const properties = inputSchema.properties && typeof inputSchema.properties === "object" && !Array.isArray(inputSchema.properties) ? (inputSchema.properties as Record) : {}; return { ...invocation, requestExample: { ...invocation.requestExample, image: [], }, inputSchema: { ...inputSchema, properties: { ...properties, image: { items: { type: "string" }, type: "array" }, aspect_ratio: { type: "string" }, ...(hasVerifiedVip98kGptImage25ResolutionTiers(modelId) ? { size: { enum: ["1024x1024", "2048x2048", "2880x2880"], type: "string", }, } : {}), }, }, }; } /** * A frontend alias may resolve to a different VIP98K tool at runtime. In that * case the selected model's invocation cannot be reused because its endpoint * and reference-image field belong to the alias rather than the resolved tool. */ export function resolveVip98kTaskInvocation( modelId: string, configuredInvocation: Vip98kTaskInvocation | null ) { const normalizedModelId = modelId.trim().toLowerCase(); if (hasVerifiedVip98kOpenAiImageArrayContract(normalizedModelId)) { const invocation = configuredInvocation && invocationTargetsModel(configuredInvocation, normalizedModelId) ? configuredInvocation : createVerifiedOpenAiImageInvocation(modelId); return addVerifiedOpenAiImageFields(invocation, normalizedModelId); } const processingRequest = KNOWN_IMAGE_PROCESSING_TOOL_REQUESTS[normalizedModelId]; if (processingRequest) { if (invocationTargetsModel(configuredInvocation, normalizedModelId)) { return configuredInvocation; } return createKnownProcessingToolInvocation(modelId, processingRequest); } const knownImageInput = KNOWN_IMAGE_TOOL_INPUTS[normalizedModelId]; if (!knownImageInput) return configuredInvocation; if (invocationTargetsModel(configuredInvocation, normalizedModelId)) { return configuredInvocation; } return createKnownImageToolInvocation(modelId, knownImageInput); }