From 7370d92efea5ed4cdcc03ae3607c6c1bd8379325 Mon Sep 17 00:00:00 2001 From: shrimbly Date: Fri, 27 Feb 2026 23:14:18 +1300 Subject: [PATCH] feat: add Gemini video model schemas for Veo 3.1 Add getGeminiVideoSchema() function returning parameter schemas (aspect ratio, duration, resolution, negative prompt, seed) and input definitions for all 4 Veo model variants. Wire into GET handler with gemini provider support. Co-Authored-By: Claude Opus 4.6 --- src/app/api/models/[modelId]/route.ts | 56 +++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/src/app/api/models/[modelId]/route.ts b/src/app/api/models/[modelId]/route.ts index 3c6ac278..ce6aa9ec 100644 --- a/src/app/api/models/[modelId]/route.ts +++ b/src/app/api/models/[modelId]/route.ts @@ -891,6 +891,47 @@ function getKieSchema(modelId: string): ExtractedSchema { return schemas[modelId] || { parameters: [], inputs: [] }; } +/** + * Get schema for Gemini video models (native Veo via Gemini API) + * Returns null if the model is not a Gemini video model. + */ +function getGeminiVideoSchema(modelId: string): ExtractedSchema | null { + const commonParams: ModelParameter[] = [ + { name: "aspectRatio", type: "string", description: "Output aspect ratio", enum: ["16:9", "9:16"], default: "16:9" }, + { name: "durationSeconds", type: "string", description: "Video duration in seconds", enum: ["4", "6", "8"], default: "8" }, + { name: "resolution", type: "string", description: "Output resolution", enum: ["720p", "1080p", "4k"], default: "720p" }, + { name: "negativePrompt", type: "string", description: "What to avoid in the generated video" }, + { name: "seed", type: "integer", description: "Random seed for reproducibility", minimum: 0 }, + ]; + + const schemas: Record = { + "veo-3.1/text-to-video": { + parameters: commonParams, + inputs: [{ name: "prompt", type: "text", required: true, label: "Prompt" }], + }, + "veo-3.1/image-to-video": { + parameters: commonParams, + inputs: [ + { name: "prompt", type: "text", required: true, label: "Prompt" }, + { name: "image", type: "image", required: true, label: "Image" }, + ], + }, + "veo-3.1-fast/text-to-video": { + parameters: commonParams, + inputs: [{ name: "prompt", type: "text", required: true, label: "Prompt" }], + }, + "veo-3.1-fast/image-to-video": { + parameters: commonParams, + inputs: [ + { name: "prompt", type: "text", required: true, label: "Prompt" }, + { name: "image", type: "image", required: true, label: "Image" }, + ], + }, + }; + + return schemas[modelId] ?? null; +} + /** * Get static schema for WaveSpeed models (fallback when dynamic schema not available) */ @@ -1125,11 +1166,11 @@ export async function GET( const decodedModelId = decodeURIComponent(modelId); const provider = request.nextUrl.searchParams.get("provider") as ProviderType | null; - if (!provider || (provider !== "replicate" && provider !== "fal" && provider !== "kie" && provider !== "wavespeed")) { + if (!provider || (provider !== "replicate" && provider !== "fal" && provider !== "kie" && provider !== "wavespeed" && provider !== "gemini")) { return NextResponse.json( { success: false, - error: "Invalid or missing provider. Use ?provider=replicate, ?provider=fal, ?provider=kie, or ?provider=wavespeed", + error: "Invalid or missing provider. Use ?provider=replicate, ?provider=fal, ?provider=kie, ?provider=wavespeed, or ?provider=gemini", }, { status: 400 } ); @@ -1150,7 +1191,16 @@ export async function GET( try { let result: ExtractedSchema; - if (provider === "replicate") { + if (provider === "gemini") { + // Gemini video models use hardcoded schemas + const geminiVideoSchema = getGeminiVideoSchema(decodedModelId); + if (geminiVideoSchema) { + result = geminiVideoSchema; + } else { + // Gemini image models don't use schema endpoint (params are built-in) + result = { parameters: [], inputs: [] }; + } + } else if (provider === "replicate") { // User-provided key takes precedence over env variable const apiKey = request.headers.get("X-Replicate-Key") || process.env.REPLICATE_API_KEY; if (!apiKey) {