diff --git a/lib/canvas/generate-image.ts b/lib/canvas/generate-image.ts index 73c7d239f..5df759862 100644 --- a/lib/canvas/generate-image.ts +++ b/lib/canvas/generate-image.ts @@ -1526,6 +1526,75 @@ export async function generateCanvasImage( await slot.release(); } + await recordAiModelHealthFailure({ + model: selectedModel.model, + provider, + error: caughtError, + context: debugLabel, + elapsedMs: Date.now() - startedAt, + metadata: { + aspectRatio, + imageSize, + imageCount, + requestedReferenceImageCount: normalizedReferenceImages.length, + reachableReferenceImageCount: reachableReferenceImages.length, + effectiveReferenceImageCount: effectiveReferenceImages.length, + droppedReferenceImageCount: unreachableReferenceImages.length, + ...debugMeta, + }, + }).catch((healthError) => { + console.warn("[Canvas Generate Image] Record model health failure failed:", { + selectedModelRef: selectedModel.model.id, + selectedModelId: selectedModel.model.modelId, + error: healthError instanceof Error ? healthError.message : String(healthError), + }); + }); + + const failedModelRefs = Array.from( + new Set([ + ...(Array.isArray(request.internalFailoverTriedModelRefs) + ? request.internalFailoverTriedModelRefs + : []), + selectedModel.model.id, + selectedModel.model.modelId, + ].filter(Boolean)) + ); + const allowFailover = request.allowFailover !== false; + const shouldFailover = + shouldFailoverAiModelError(caughtError) || + (Boolean(debugMeta?.detailFixedModelApplied) && Boolean(caughtError)); + const fallbackModel = allowFailover && shouldFailover + ? findCanvasFailoverModel({ + selectedModel, + imageModels, + failedModelRefs, + }) + : null; + + if (fallbackModel) { + console.warn("[Canvas Generate Image] Failover to alternate image model:", { + debugLabel, + failedModelRef: selectedModel.model.id, + failedModelId: selectedModel.model.modelId, + fallbackModelRef: fallbackModel.model.id, + fallbackModelId: fallbackModel.model.modelId, + fallbackProviderType: fallbackModel.provider.type, + error: caughtError instanceof Error ? caughtError.message : String(caughtError), + ...debugMeta, + }); + + return await generateCanvasImage({ + ...request, + model: fallbackModel.model.id, + internalFailoverTriedModelRefs: failedModelRefs, + debugMeta: { + ...debugMeta, + failoverFromModelId: selectedModel.model.modelId, + failoverFromModelRef: selectedModel.model.id, + }, + }); + } + if (scaleAdjustVerification) { try { const sourceImage = await loadScaleAdjustSourceImage(scaleAdjustVerification); @@ -1533,38 +1602,13 @@ export async function generateCanvasImage( const fallbackImage = await createScaleAdjustFallbackUpload({ debugLabel, imageIndex: 1, - reason: "provider-error", + reason: allowFailover ? "provider-error-after-failover-exhausted" : "provider-error", sourceBuffer: sourceImage.sourceBuffer, sourceHeight: sourceImage.sourceHeight, sourceWidth: sourceImage.sourceWidth, verification: scaleAdjustVerification, }); - await recordAiModelHealthFailure({ - model: selectedModel.model, - provider, - error: caughtError, - context: debugLabel, - elapsedMs: Date.now() - startedAt, - metadata: { - aspectRatio, - imageSize, - imageCount, - requestedReferenceImageCount: normalizedReferenceImages.length, - reachableReferenceImageCount: reachableReferenceImages.length, - effectiveReferenceImageCount: effectiveReferenceImages.length, - droppedReferenceImageCount: unreachableReferenceImages.length, - scaleAdjustFallbackReason: "provider-error", - ...debugMeta, - }, - }).catch((healthError) => { - console.warn("[Canvas Generate Image] Record model health failure failed:", { - selectedModelRef: selectedModel.model.id, - selectedModelId: selectedModel.model.modelId, - error: healthError instanceof Error ? healthError.message : String(healthError), - }); - }); - recordCanvasImageModelExecutionDuration( resolvedModelKey, Date.now() - startedAt @@ -1602,71 +1646,5 @@ export async function generateCanvasImage( } } - await recordAiModelHealthFailure({ - model: selectedModel.model, - provider, - error: caughtError, - context: debugLabel, - elapsedMs: Date.now() - startedAt, - metadata: { - aspectRatio, - imageSize, - imageCount, - requestedReferenceImageCount: normalizedReferenceImages.length, - reachableReferenceImageCount: reachableReferenceImages.length, - effectiveReferenceImageCount: effectiveReferenceImages.length, - droppedReferenceImageCount: unreachableReferenceImages.length, - ...debugMeta, - }, - }).catch((healthError) => { - console.warn("[Canvas Generate Image] Record model health failure failed:", { - selectedModelRef: selectedModel.model.id, - selectedModelId: selectedModel.model.modelId, - error: healthError instanceof Error ? healthError.message : String(healthError), - }); - }); - - const failedModelRefs = Array.from( - new Set([ - ...(Array.isArray(request.internalFailoverTriedModelRefs) - ? request.internalFailoverTriedModelRefs - : []), - selectedModel.model.id, - selectedModel.model.modelId, - ].filter(Boolean)) - ); - const allowFailover = request.allowFailover !== false; - const fallbackModel = allowFailover && shouldFailoverAiModelError(caughtError) - ? findCanvasFailoverModel({ - selectedModel, - imageModels, - failedModelRefs, - }) - : null; - - if (fallbackModel) { - console.warn("[Canvas Generate Image] Failover to alternate image model:", { - debugLabel, - failedModelRef: selectedModel.model.id, - failedModelId: selectedModel.model.modelId, - fallbackModelRef: fallbackModel.model.id, - fallbackModelId: fallbackModel.model.modelId, - fallbackProviderType: fallbackModel.provider.type, - error: caughtError instanceof Error ? caughtError.message : String(caughtError), - ...debugMeta, - }); - - return await generateCanvasImage({ - ...request, - model: fallbackModel.model.id, - internalFailoverTriedModelRefs: failedModelRefs, - debugMeta: { - ...debugMeta, - failoverFromModelId: selectedModel.model.modelId, - failoverFromModelRef: selectedModel.model.id, - }, - }); - } - throw caughtError; }