diff --git a/lib/ai/vip98k-models.ts b/lib/ai/vip98k-models.ts index 37133d6d8..6fc9c19f7 100644 --- a/lib/ai/vip98k-models.ts +++ b/lib/ai/vip98k-models.ts @@ -181,6 +181,7 @@ const IMAGE_TOOL_OPERATION_BY_MODEL_ID: Record = { // available to backend routing but remain hidden until their 2026+ release is verified. const VERIFIED_2026_IMAGE_MODEL_PATTERNS = [ /^nano-banana-2(?:$|[-/])/, + /^nano-banana-fast$/, /^nano-banana-pro$/, /^gpt-image-2(?:$|[-/])/, /^alibaba\/wan-2\.(?:6|7)(?:$|[-/])/, @@ -425,8 +426,14 @@ export function isVip98kCatalogModelAvailable(model: Vip98kCatalogModel) { export function isVip98kCatalogModelEligible(model: Vip98kCatalogModel) { if (!getModelId(model) || !isVip98kCatalogModelAvailable(model)) return false; const type = resolvePresetType(model); - if (type !== "image" && type !== "video") return true; - return hasMediaReferenceInput(model, type); + if (type === "image") { + return ( + hasMediaReferenceInput(model, type) || + isVip98kTextToImageCatalogModel(model) + ); + } + if (type === "video") return hasMediaReferenceInput(model, type); + return true; } export function filterVip98kCatalogModels(models: Vip98kCatalogModel[]) { @@ -652,7 +659,7 @@ function createImagePreset( type: "image", supportsVision: supportsReferenceImages, supportsImageGen: true, - isHidden: !isVip98kImageModelVisible(model) || !supportsReferenceImages, + isHidden: !isVip98kImageModelVisible(model), capabilities: { provider: "vip98k", kind: "image", diff --git a/scripts/configure-image-provider-allowlist.ts b/scripts/configure-image-provider-allowlist.ts index e98b5a691..9b0bfb550 100644 --- a/scripts/configure-image-provider-allowlist.ts +++ b/scripts/configure-image-provider-allowlist.ts @@ -19,12 +19,7 @@ function toJsonValue(value: unknown): postgres.JSONValue { } function isQualifiedVip98kImagePreset(preset: Vip98kModelPreset) { - const capabilities = preset.capabilities as Record; - return ( - preset.supportsImageGen && - !preset.isHidden && - capabilities.supportsMultiImageReference === true - ); + return preset.supportsImageGen && !preset.isHidden; } async function main() { @@ -40,7 +35,7 @@ async function main() { }); const qualifiedVip98kPresets = presets.filter(isQualifiedVip98kImagePreset); if (qualifiedVip98kPresets.length === 0) { - throw new Error("VIP98K catalog contains no qualified multi-image model"); + throw new Error("VIP98K catalog contains no qualified image model"); } const sql = postgres(databaseUrl, { prepare: false, max: 1 }); @@ -64,23 +59,15 @@ async function main() { Array<{ id: string; modelId: string; - healthStatus: string | null; }> >` - select id, "modelId", "healthStatus" + select id, "modelId" from "AiModel" where "providerId" = ${vip98kProvider.id}::uuid `; const existingVip98kByModelId = new Map( existingVip98kModels.map((model) => [model.modelId, model]), ); - const autoDisabledQualifiedModelIds = qualifiedVip98kPresets - .filter( - (preset) => - existingVip98kByModelId.get(preset.modelId)?.healthStatus === - "auto_disabled", - ) - .map((preset) => preset.modelId); const disallowedImageModels = await sql< Array<{ id: string; providerType: string; modelId: string }> @@ -123,7 +110,9 @@ async function main() { mode: apply ? "apply" : "dry-run", allowedImageProviderTypes: ALLOWED_IMAGE_PROVIDER_TYPES, qualifiedVip98kModels: qualifiedVip98kPresets.map((preset) => preset.modelId), - autoDisabledVip98kModelsPreserved: autoDisabledQualifiedModelIds, + protectedVip98kModels: qualifiedVip98kPresets.map( + (preset) => preset.modelId, + ), disallowedImageModelsToDisable: disallowedImageModels.length, staleVip98kImageModelsToDisable: staleVip98kImageModels.length, imageOnlyProvidersToDisable: imageOnlyProviders.map((provider) => ({ @@ -148,11 +137,21 @@ async function main() { update "AiProvider" set "isEnabled" = true, "updatedAt" = now() where type = 'alibaba-image' `; + await tx` + update "AiModel" m set + "isEnabled" = true, "excludeFromHealthBan" = true, + "healthStatus" = 'healthy', "healthConsecutiveFailures" = 0, + "healthLastEventType" = null, "healthLastError" = null, + "healthAutoDisabledAt" = null, "healthAutoDisabledReason" = null, + "healthRecoverySuccessStreak" = 0, + "healthObservationStartedAt" = null, + "healthUpdatedAt" = now(), "updatedAt" = now() + from "AiProvider" p + where p.id = m."providerId" and p.type = 'alibaba-image' + and m."supportsImageGen" and not m."isHidden" + `; for (const preset of qualifiedVip98kPresets) { - const preserveAutoDisabled = autoDisabledQualifiedModelIds.includes( - preset.modelId, - ); const capabilities = tx.json(toJsonValue(preset.capabilities)); const defaultParams = tx.json(toJsonValue(preset.defaultParams)); const imageGenConfig = preset.imageGenConfig @@ -164,7 +163,12 @@ async function main() { update "AiModel" set "displayName" = ${preset.displayName}, description = ${preset.description}, "supportsVision" = ${preset.supportsVision}, "supportsImageGen" = true, - "isEnabled" = ${!preserveAutoDisabled}, "isHidden" = false, + "isEnabled" = true, "isHidden" = false, + "excludeFromHealthBan" = true, "healthStatus" = 'healthy', + "healthConsecutiveFailures" = 0, "healthLastEventType" = null, + "healthLastError" = null, "healthAutoDisabledAt" = null, + "healthAutoDisabledReason" = null, "healthRecoverySuccessStreak" = 0, + "healthObservationStartedAt" = null, "healthUpdatedAt" = now(), "sortOrder" = ${preset.sortOrder}, capabilities = ${capabilities}, "defaultParams" = ${defaultParams}, "imageGenConfig" = ${imageGenConfig}, "fallbackModelId1" = null, @@ -176,11 +180,13 @@ async function main() { insert into "AiModel" ( "providerId", "modelId", "displayName", description, "supportsVision", "supportsImageGen", "isEnabled", "isHidden", "sortOrder", capabilities, - "defaultParams", "imageGenConfig", "isDefault", "isDefaultTextEditModel" + "defaultParams", "imageGenConfig", "isDefault", "isDefaultTextEditModel", + "excludeFromHealthBan", "healthStatus" ) values ( ${vip98kProvider.id}::uuid, ${preset.modelId}, ${preset.displayName}, ${preset.description}, ${preset.supportsVision}, true, true, false, - ${preset.sortOrder}, ${capabilities}, ${defaultParams}, ${imageGenConfig}, false, false + ${preset.sortOrder}, ${capabilities}, ${defaultParams}, ${imageGenConfig}, + false, false, true, 'healthy' ) `; } @@ -227,9 +233,7 @@ async function main() { if (invalidModels.length > 0) { throw new Error("Disallowed image models remain enabled after apply"); } - const expectedActiveVip98kCount = - qualifiedVip98kPresets.length - autoDisabledQualifiedModelIds.length; - if (activeVip98kModels.length !== expectedActiveVip98kCount) { + if (activeVip98kModels.length !== qualifiedVip98kPresets.length) { throw new Error("VIP98K active image model count does not match qualified catalog"); } console.log( diff --git a/tests/routes/vip98k-reference-routing.test.ts b/tests/routes/vip98k-reference-routing.test.ts index 79ebf59e6..de58a98b6 100644 --- a/tests/routes/vip98k-reference-routing.test.ts +++ b/tests/routes/vip98k-reference-routing.test.ts @@ -355,10 +355,10 @@ test.describe("VIP98K reference-image routing", () => { ).toBe(false); }); - test("excludes text-only VIP98K image models from editing workflows", () => { + test("publishes text-only VIP98K models without reference-image support", () => { const [preset] = buildVip98kModelPresets([ { - id: "nano-banana-pro", + id: "gpt-image-2-vip", type: "image", capabilities: ["openai.images.generations", "text_to_image"], invocation: { @@ -366,12 +366,22 @@ test.describe("VIP98K reference-image routing", () => { method: "POST", path: "/v1/images/generations", content_type: "application/json", - request_example: { model: "nano-banana-pro", n: 1, prompt: "Generate" }, + request_example: { model: "gpt-image-2-vip", n: 1, prompt: "Generate" }, }, }, ]); - expect(preset).toBeUndefined(); + expect(preset).toMatchObject({ + modelId: "gpt-image-2-vip", + supportsVision: false, + supportsImageGen: true, + isHidden: false, + capabilities: { + supportedModes: ["text-to-image"], + supportsMultiImageReference: false, + maxReferenceImages: 0, + }, + }); }); test("does not inherit multi-image support from broad authorized metadata", () => { @@ -395,6 +405,15 @@ test.describe("VIP98K reference-image routing", () => { }, ]); - expect(presets).toEqual([]); + expect(presets).toHaveLength(1); + expect(presets[0]).toMatchObject({ + modelId: "nano-banana-2-lite", + supportsVision: false, + capabilities: { + supportedModes: ["text-to-image"], + supportsMultiImageReference: false, + maxReferenceImages: 0, + }, + }); }); });