import { getProviderCredentials, markAccountUnavailable, clearAccountError, extractApiKey, isValidApiKey, } from "../services/auth.js"; import { getSettings } from "@/lib/localDb"; import { getModelInfo } from "../services/model.js"; import { handleImageGenerationCore } from "open-sse/handlers/imageGenerationCore.js"; import { errorResponse, unavailableResponse } from "open-sse/utils/error.js"; import { HTTP_STATUS } from "open-sse/config/runtimeConfig.js"; import { updateProviderCredentials, checkAndRefreshToken } from "../services/tokenRefresh.js"; // Providers that don't require credentials (noAuth) const NO_AUTH_PROVIDERS = new Set(["sdwebui", "comfyui"]); /** * Handle image generation request * @param {Request} request */ export async function handleImageGeneration(request) { let body; try { body = await request.json(); } catch { return errorResponse(HTTP_STATUS.BAD_REQUEST, "Invalid JSON body"); } const preferredConnectionId = request.headers.get("x-connection-id") || null; const wantsStream = (request.headers.get("accept") || "").includes("text/event-stream"); const modelStr = body.model; const apiKey = extractApiKey(request); const settings = await getSettings(); if (settings.requireApiKey) { if (!apiKey) return errorResponse(HTTP_STATUS.UNAUTHORIZED, "Missing API key"); const valid = await isValidApiKey(apiKey); if (!valid) return errorResponse(HTTP_STATUS.UNAUTHORIZED, "Invalid API key"); } if (!modelStr) return errorResponse(HTTP_STATUS.BAD_REQUEST, "Missing model"); if (!body.prompt) return errorResponse(HTTP_STATUS.BAD_REQUEST, "Missing required field: prompt"); const modelInfo = await getModelInfo(modelStr); if (!modelInfo.provider) return errorResponse(HTTP_STATUS.BAD_REQUEST, "Invalid model format"); const { provider, model } = modelInfo; // noAuth providers — no credential needed if (NO_AUTH_PROVIDERS.has(provider)) { const result = await handleImageGenerationCore({ body, modelInfo: { provider, model }, credentials: null, }); if (result.success) return result.response; return errorResponse(result.status || HTTP_STATUS.BAD_GATEWAY, result.error || "Image generation failed"); } // Credentialed providers — fallback loop const excludeConnectionIds = new Set(); let lastError = null; let lastStatus = null; while (true) { const credentials = await getProviderCredentials(provider, excludeConnectionIds, model, { preferredConnectionId }); if (!credentials || credentials.allRateLimited) { if (credentials?.allRateLimited) { const errorMsg = lastError || credentials.lastError || "Unavailable"; const status = lastStatus || Number(credentials.lastErrorCode) || HTTP_STATUS.SERVICE_UNAVAILABLE; return unavailableResponse(status, `[${provider}/${model}] ${errorMsg}`, credentials.retryAfter, credentials.retryAfterHuman); } if (excludeConnectionIds.size === 0) { return errorResponse(HTTP_STATUS.BAD_REQUEST, `No credentials for provider: ${provider}`); } return errorResponse(lastStatus || HTTP_STATUS.SERVICE_UNAVAILABLE, lastError || "All accounts unavailable"); } const refreshedCredentials = await checkAndRefreshToken(provider, credentials); const result = await handleImageGenerationCore({ body, modelInfo: { provider, model }, credentials: refreshedCredentials, streamToClient: wantsStream, onCredentialsRefreshed: async (newCreds) => { await updateProviderCredentials(credentials.connectionId, { accessToken: newCreds.accessToken, refreshToken: newCreds.refreshToken, providerSpecificData: newCreds.providerSpecificData, testStatus: "active" }); }, onRequestSuccess: async () => { await clearAccountError(credentials.connectionId, credentials, model); } }); if (result.success) return result.response; const { shouldFallback } = await markAccountUnavailable(credentials.connectionId, result.status, result.error, provider, model); if (shouldFallback) { excludeConnectionIds.add(credentials.connectionId); lastError = result.error; lastStatus = result.status; continue; } return result.response; } }