Skip to content

Commit fa3b97b

Browse files
committed
fix(video): use a real Veo model id (was 404ing on a fabricated id)
Generation 404'd: POST …/models/{model}:predictLongRunning returned Not Found because the default model id `veo-3.1-fast-generate-001` does not exist. It was introduced based on a review note to "migrate 3.0-fast → 3.1-fast", but the real Gemini Veo ids (per the @ai-sdk/google VideoModelId union) are veo-3.0-fast-generate-001 (stable) and veo-3.1-fast-generate-preview (preview) — there is no `-3.1-fast-…-001`. - Default back to the stable `veo-3.0-fast-generate-001`; register `veo-3.1-fast-generate-preview` as the newer option (both real ids). - Surface a specific error on a 404 ("Video model X is unavailable for this API key") instead of the generic "request failed", and log the model id. - Add a test asserting the default id is actually registered as video-capable, so a fabricated/typo'd id can't regress silently. Gate green: tsc/eslint/prettier, 1247 vitest, build.
1 parent de937d5 commit fa3b97b

4 files changed

Lines changed: 40 additions & 20 deletions

File tree

.env.example

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ ELEVENLABS_API_KEY=
4343
# AI_IMAGE_MODEL=
4444
# Where generated images are written on disk (default: .data/images/):
4545
# A2UI_IMAGE_DIR=
46-
# Pin the on-demand video model (default veo-3.1-fast-generate-001). Video uses
46+
# Pin the on-demand video model (default veo-3.0-fast-generate-001; the 3.1 fast
47+
# model is preview-suffixed: veo-3.1-fast-generate-preview). Video uses
4748
# the same GOOGLE_GENERATIVE_AI_API_KEY / GEMINI_API_KEY as images; it is ONLY
4849
# generated on demand (Video Lab + explicit per-component button), never bundled
4950
# into UI generation, because each Veo clip is comparatively expensive:

src/lib/ai/model-registry.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -316,12 +316,13 @@ export const MODEL_REGISTRY: Record<string, ModelInfo> = {
316316
},
317317
// Veo Fast — long-running text→video. On-demand ONLY (Video Lab + explicit
318318
// per-component button); deliberately never invoked during UI/chat generation
319-
// the way images are, because each clip is comparatively expensive. 3.1-fast
320-
// is the current default; 3.0-fast is kept registered for back-compat but is
321-
// discontinued by Google on 2026-06-30, so the default points at 3.1.
322-
"veo-3.1-fast-generate-001": {
323-
id: "veo-3.1-fast-generate-001",
324-
name: "Veo 3.1 Fast",
319+
// the way images are, because each clip is comparatively expensive. Model ids
320+
// here MUST match Google's actual Veo ids (the @ai-sdk/google VideoModelId
321+
// union): the stable fast id is `veo-3.0-fast-generate-001` (the default); the
322+
// 3.1 fast model is preview-suffixed (`veo-3.1-fast-generate-preview`).
323+
"veo-3.0-fast-generate-001": {
324+
id: "veo-3.0-fast-generate-001",
325+
name: "Veo 3 Fast",
325326
provider: "google",
326327
capabilities: {
327328
chat: false,
@@ -332,9 +333,9 @@ export const MODEL_REGISTRY: Record<string, ModelInfo> = {
332333
tools: false,
333334
},
334335
},
335-
"veo-3.0-fast-generate-001": {
336-
id: "veo-3.0-fast-generate-001",
337-
name: "Veo 3 Fast (legacy)",
336+
"veo-3.1-fast-generate-preview": {
337+
id: "veo-3.1-fast-generate-preview",
338+
name: "Veo 3.1 Fast (preview)",
338339
provider: "google",
339340
capabilities: {
340341
chat: false,

src/lib/ai/video.test.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
pollVideoOperation,
1414
downloadVideo,
1515
} from "./video";
16+
import { getModelInfo } from "./model-registry";
1617

1718
describe("buildVideoPrompt", () => {
1819
it("folds the aesthetic's cinematic style into the prompt", () => {
@@ -32,16 +33,24 @@ describe("resolveVideoModel", () => {
3233
delete process.env.AI_VIDEO_MODEL;
3334
});
3435

35-
it("defaults to veo-3.1-fast-generate-001", () => {
36-
expect(resolveVideoModel()).toBe("veo-3.1-fast-generate-001");
36+
it("defaults to the stable veo-3.0-fast-generate-001 id", () => {
37+
expect(resolveVideoModel()).toBe("veo-3.0-fast-generate-001");
38+
});
39+
40+
it("defaults to a model that is actually registered as video-capable", () => {
41+
// Guards against a fabricated/typo'd default id (a wrong id 404s on Veo).
42+
const id = resolveVideoModel();
43+
expect(getModelInfo(id)?.capabilities.videoGen).toBe(true);
3744
});
3845

3946
it("honors an explicit video-capable model", () => {
40-
expect(resolveVideoModel("veo-3.1-fast-generate-001")).toBe("veo-3.1-fast-generate-001");
47+
expect(resolveVideoModel("veo-3.1-fast-generate-preview")).toBe(
48+
"veo-3.1-fast-generate-preview"
49+
);
4150
});
4251

4352
it("ignores a non-video model id and falls back to the default", () => {
44-
expect(resolveVideoModel("imagen-4.0-generate-001")).toBe("veo-3.1-fast-generate-001");
53+
expect(resolveVideoModel("imagen-4.0-generate-001")).toBe("veo-3.0-fast-generate-001");
4554
});
4655
});
4756

@@ -83,7 +92,7 @@ describe("video generation REST integration", () => {
8392
expect(result.ok).toBe(true);
8493
expect(result.operationName).toBe("operations/abc-123");
8594
const [url, init] = mockFetch.mock.calls[0];
86-
expect(url).toContain("veo-3.1-fast-generate-001:predictLongRunning");
95+
expect(url).toContain("veo-3.0-fast-generate-001:predictLongRunning");
8796
expect(init.headers["x-goog-api-key"]).toBe("k");
8897
const sent = JSON.parse(init.body);
8998
expect(sent.instances[0].prompt).toMatch(/neon alley/);

src/lib/ai/video.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@ import type { AestheticId } from "@/lib/aesthetic/types";
2727
*/
2828

2929
const BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
30-
// Default to Veo 3.1 Fast: the 3.0-fast generation is discontinued by Google on
31-
// 2026-06-30, and 3.1-fast is its drop-in successor. Override with AI_VIDEO_MODEL.
32-
const DEFAULT_VIDEO_MODEL = "veo-3.1-fast-generate-001";
30+
// Default to Veo 3 Fast — the current stable fast model id on the Gemini API.
31+
// (The 3.1 fast model is preview-suffixed: `veo-3.1-fast-generate-preview`.)
32+
// Override with AI_VIDEO_MODEL. The id MUST be a real Gemini Veo id or the
33+
// predictLongRunning endpoint 404s.
34+
const DEFAULT_VIDEO_MODEL = "veo-3.0-fast-generate-001";
3335

3436
/** Aspect ratios Veo accepts; anything else is dropped to the provider default. */
3537
const VEO_ASPECTS = new Set(["16:9", "9:16"]);
@@ -142,8 +144,15 @@ export async function startVideoGeneration(opts: {
142144

143145
if (!res.ok) {
144146
const detail = await res.text().catch(() => "");
145-
console.error("[Veo] start failed:", res.status, detail);
146-
return { ok: false, status: res.status, error: "Video generation request failed" };
147+
console.error("[Veo] start failed:", res.status, model, detail);
148+
// 404 from predictLongRunning means the model id is wrong or video
149+
// generation isn't enabled for this key — call that out specifically so
150+
// it isn't mistaken for a transient failure.
151+
const error =
152+
res.status === 404
153+
? `Video model "${model}" is unavailable for this API key`
154+
: "Video generation request failed";
155+
return { ok: false, status: res.status, error };
147156
}
148157

149158
const data = (await res.json()) as { name?: string };

0 commit comments

Comments
 (0)