diff --git a/src/app/api/generate/providers/kie.ts b/src/app/api/generate/providers/kie.ts index cfaa40b1..754ef2b6 100644 --- a/src/app/api/generate/providers/kie.ts +++ b/src/app/api/generate/providers/kie.ts @@ -397,9 +397,12 @@ export async function pollKieTaskCompletion( taskId: string, ): Promise<{ success: boolean; data?: Record; error?: string }> { const maxWaitTime = 10 * 60 * 1000; // 10 minutes for video - const pollInterval = 2000; // 2 seconds + let pollInterval = 2000; // start at 2s + const maxInterval = 10000; // cap at 10s const startTime = Date.now(); let lastStatus = ""; + let consecutiveErrors = 0; + const maxConsecutiveErrors = 5; const pollUrl = `https://api.kie.ai/api/v1/jobs/recordInfo?taskId=${encodeURIComponent(taskId)}`; @@ -410,31 +413,63 @@ export async function pollKieTaskCompletion( await new Promise(resolve => setTimeout(resolve, pollInterval)); - const response = await fetch(pollUrl, { - headers: { - "Authorization": `Bearer ${apiKey}`, - }, - }); + let response: Response; + try { + response = await fetch(pollUrl, { + headers: { + "Authorization": `Bearer ${apiKey}`, + }, + }); + } catch (err) { + consecutiveErrors++; + console.warn(`[API:${requestId}] Kie poll network error (${consecutiveErrors}/${maxConsecutiveErrors}):`, err); + if (consecutiveErrors >= maxConsecutiveErrors) { + return { success: false, error: `Polling failed after ${maxConsecutiveErrors} consecutive network errors` }; + } + pollInterval = Math.min(pollInterval + 1000, maxInterval); + continue; + } if (!response.ok) { - // 404/422 can happen transiently when the task isn't registered yet — retry - if (response.status === 404 || response.status === 422) { - console.log(`[API:${requestId}] Kie poll returned ${response.status}, task not ready yet — retrying`); + // 404/422 = task not registered yet, 429 = rate limited, 5xx = server error — all transient + if (response.status === 404 || response.status === 422 || response.status === 429 || response.status >= 500) { + consecutiveErrors++; + console.log(`[API:${requestId}] Kie poll returned ${response.status} (${consecutiveErrors}/${maxConsecutiveErrors}) — retrying`); + if (consecutiveErrors >= maxConsecutiveErrors) { + return { success: false, error: `Polling failed after ${maxConsecutiveErrors} consecutive HTTP ${response.status} errors` }; + } + pollInterval = Math.min(pollInterval + 1000, maxInterval); continue; } return { success: false, error: `Failed to poll status: ${response.status}` }; } - const result = await response.json(); + let result: Record; + try { + result = await response.json(); + } catch (err) { + consecutiveErrors++; + console.warn(`[API:${requestId}] Kie poll JSON parse error (${consecutiveErrors}/${maxConsecutiveErrors}):`, err); + if (consecutiveErrors >= maxConsecutiveErrors) { + return { success: false, error: `Polling failed after ${maxConsecutiveErrors} consecutive parse errors` }; + } + pollInterval = Math.min(pollInterval + 1000, maxInterval); + continue; + } + + // Reset on any successful poll response + consecutiveErrors = 0; // Kie API can return HTTP 200 with code != 200 (e.g. "recordInfo is null") if (result.code && result.code !== 200) { - console.log(`[API:${requestId}] Kie poll returned code ${result.code}: ${result.msg || ""} — retrying`); + console.log(`[API:${requestId}] Kie poll returned code ${result.code}: ${(result as Record).msg || ""} — retrying`); + pollInterval = Math.min(pollInterval + 1000, maxInterval); continue; } // Kie API returns "state" in result.data.state (not "status") - const state = (result.data?.state || result.state || result.status || "").toUpperCase(); + const data = result.data as Record | undefined; + const state = ((data?.state || result.state || result.status || "") as string).toUpperCase(); if (state !== lastStatus) { console.log(`[API:${requestId}] Kie task state: ${state}`); @@ -442,16 +477,17 @@ export async function pollKieTaskCompletion( } if (state === "SUCCESS" || state === "COMPLETED") { - return { success: true, data: result.data || result }; + return { success: true, data: data || result }; } if (state === "FAIL" || state === "FAILED" || state === "ERROR") { console.error(`[API:${requestId}] Kie task failed. Full response:`, JSON.stringify(result).substring(0, 1000)); - const errorMessage = result.data?.failMsg || result.data?.errorMessage || result.error || result.message || "Generation failed"; - return { success: false, error: errorMessage }; + const errorMessage = data?.failMsg || data?.errorMessage || result.error || result.message || "Generation failed"; + return { success: false, error: errorMessage as string }; } // Continue polling for: WAITING, QUEUING, GENERATING, PROCESSING, etc. + pollInterval = Math.min(pollInterval + 1000, maxInterval); } }