feat(sse): add DeepInfra as a video-generation provider (#6653) (#7598)

Registers `deepinfra` in the video-gen registry, reusing the DeepInfra
native /v1/inference/{model} endpoint already proven for reranking in
this codebase (same host, Bearer auth, non-OpenAI response shape).
Confirmed synchronous against DeepInfra's own docs (POST {prompt} ->
{video_url, seed, request_id, inference_status}), so no polling loop
is needed. Reuses the already-registered `deepinfra` API-key provider
credential (chat) — no new credential/OAuth flow.

To keep the frozen videoGeneration.ts file-size ratchet from growing,
the new deepinfra-video adapter lives in its own co-located module
(open-sse/handlers/videoGeneration/deepinfraHandler.ts, following the
existing googleFlowHandler.ts pattern), and the pre-existing Leonardo
handler was extracted into videoGeneration/leonardoHandler.ts (pure
code move, no behavior change) to make room.
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-17 12:00:18 -03:00
committed by GitHub
parent 93e217e763
commit b0b40a3283
6 changed files with 529 additions and 103 deletions

View File

@@ -0,0 +1 @@
- feat(sse): add DeepInfra as a video-generation provider via its native synchronous inference endpoint (#6653)

View File

@@ -193,6 +193,23 @@ export const VIDEO_PROVIDERS: Record<string, VideoProvider> = {
models: RUNWAYML_SUPPORTED_VIDEO_MODELS,
},
deepinfra: {
id: "deepinfra",
// Native DeepInfra inference endpoint — same host/auth already proven for reranking
// (open-sse/config/rerankRegistry.ts). Reuses the stored deepinfra provider Bearer
// apiKey (already registered for chat) — no separate credential flow.
baseUrl: "https://api.deepinfra.com/v1/inference",
authType: "apikey",
authHeader: "bearer",
format: "deepinfra-video",
models: [
{ id: "Wan-AI/Wan2.2-T2V-A14B", name: "Wan 2.2 T2V A14B" },
{ id: "Wan-AI/Wan2.2-TI2V-5B", name: "Wan 2.2 TI2V 5B" },
{ id: "Wan-AI/Wan2.7-T2V", name: "Wan 2.7 T2V" },
{ id: "Lightricks/LTX-2.3-Distilled", name: "LTX 2.3 Distilled" },
],
},
alibaba: {
id: "alibaba",
alias: "ali",

View File

@@ -11,6 +11,8 @@ import { getVideoProvider, parseVideoModel } from "../config/videoRegistry.ts";
import { kieExecutor } from "../executors/kie.ts";
import { vertexGenerateVideo } from "../executors/vertexMedia.ts";
import { handleGoogleFlowVideoGeneration } from "./videoGeneration/googleFlowHandler.ts";
import { handleDeepinfraVideoGeneration } from "./videoGeneration/deepinfraHandler.ts";
import { handleLeonardoVideoGeneration } from "./videoGeneration/leonardoHandler.ts";
import { handleDashscopeVideoGeneration } from "./videoGeneration/dashscopeHandler.ts";
import { handleNovitaVideoGeneration } from "./videoGeneration/novitaHandler.ts";
import { handleXaiVideoGeneration } from "./videoGeneration/xaiGrokImagineHandler.ts";
@@ -106,6 +108,17 @@ export async function handleVideoGeneration({ body, credentials, log }) {
});
}
if (providerConfig.format === "deepinfra-video") {
return handleDeepinfraVideoGeneration({
model,
provider,
providerConfig,
body,
credentials,
log,
});
}
if (providerConfig.format === "dashscope-video") {
return handleDashscopeVideoGeneration({
model,
@@ -997,109 +1010,6 @@ async function handleHaiperVideoGeneration({
return { success: false, status: 504, error: "Haiper video generation timed out" };
}
async function handleLeonardoVideoGeneration({
model,
provider,
providerConfig,
body,
credentials,
log,
}) {
const startTime = Date.now();
const token = credentials?.apiKey || "";
const res = await fetch(providerConfig.baseUrl, {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
body: JSON.stringify({
modelId: "phoenix",
prompt: body.prompt,
width: 1024,
height: 576,
num_frames: 24,
}),
});
if (!res.ok) {
const errorText = await res.text();
saveCallLog({
method: "POST",
path: "/v1/videos/generations",
status: res.status,
model: `${provider}/${model}`,
provider,
duration: Date.now() - startTime,
error: errorText.slice(0, 500),
}).catch(() => {});
return { success: false, status: res.status, error: errorText };
}
const { sdGenerationJob } = await res.json();
const genId = sdGenerationJob?.generationId;
if (!genId) {
saveCallLog({
method: "POST",
path: "/v1/videos/generations",
status: 502,
model: `${provider}/${model}`,
provider,
duration: Date.now() - startTime,
error: "No generation ID returned",
}).catch(() => {});
return { success: false, status: 502, error: "No generation ID returned" };
}
const deadline = Date.now() + 300000;
while (Date.now() < deadline) {
await sleep(5000);
const statusRes = await fetch(`${providerConfig.baseUrl}/${genId}`, {
headers: { Authorization: `Bearer ${token}` },
});
const status = await statusRes.json();
const gen = status.generations_by_pk || status;
if (gen.status === "COMPLETE") {
const imgUrl = gen.generated_images?.[0]?.url;
if (imgUrl) {
const videoRes = await fetch(imgUrl);
const buf = await videoRes.arrayBuffer();
saveCallLog({
method: "POST",
path: "/v1/videos/generations",
status: 200,
model: `${provider}/${model}`,
provider,
duration: Date.now() - startTime,
}).catch(() => {});
return {
success: true,
data: {
created: Math.floor(Date.now() / 1000),
data: [{ b64_json: Buffer.from(buf).toString("base64"), format: "mp4" }],
},
};
}
}
if (gen.status === "FAILED") {
saveCallLog({
method: "POST",
path: "/v1/videos/generations",
status: 502,
model: `${provider}/${model}`,
provider,
duration: Date.now() - startTime,
error: "Leonardo video generation failed",
}).catch(() => {});
return { success: false, status: 502, error: "Leonardo video generation failed" };
}
}
saveCallLog({
method: "POST",
path: "/v1/videos/generations",
status: 504,
model: `${provider}/${model}`,
provider,
duration: Date.now() - startTime,
error: "Leonardo video generation timed out",
}).catch(() => {});
return { success: false, status: 504, error: "Leonardo video generation timed out" };
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

View File

@@ -0,0 +1,186 @@
/**
* DeepInfra native text/image-to-video generation.
*
* DeepInfra's `/v1/inference/{model}` endpoint is already proven in this codebase for
* reranking (`open-sse/handlers/rerank.ts` + `open-sse/config/rerankRegistry.ts`) — same
* host, same Bearer auth, same non-OpenAI response shape. This reuses the stored
* `deepinfra` provider credential (already registered for chat) — no new credential flow.
*
* Confirmed against DeepInfra's own docs (https://deepinfra.com/<model>/api): the call is
* synchronous — `POST {prompt}` returns `{video_url, seed, request_id, inference_status}`
* directly, no task/poll loop like `kie-video`/`dashscope-video`.
*/
import { sanitizeErrorMessage } from "../../utils/error.ts";
import { saveCallLog } from "@/lib/usageDb";
interface DeepinfraHandlerArgs {
model: string;
provider: string;
providerConfig: { baseUrl: string };
body: Record<string, unknown> & {
prompt?: unknown;
negative_prompt?: unknown;
image?: unknown;
image_url?: unknown;
seed?: unknown;
};
credentials?: { apiKey?: string; accessToken?: string } | null;
log?: {
info?: (scope: string, message: string) => void;
error?: (scope: string, message: string) => void;
} | null;
}
interface DeepinfraVideoResponse {
video_url?: unknown;
seed?: unknown;
request_id?: unknown;
inference_status?: { status?: unknown; error?: unknown } | null;
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}
/** Builds the DeepInfra `/v1/inference/{model}` request body from the OmniRoute video body. */
/* @testonly */ export function buildDeepinfraVideoRequestBody(
body: DeepinfraHandlerArgs["body"]
): Record<string, unknown> {
const payload: Record<string, unknown> = {
prompt: typeof body.prompt === "string" ? body.prompt : String(body.prompt ?? ""),
};
if (typeof body.negative_prompt === "string" && body.negative_prompt) {
payload.negative_prompt = body.negative_prompt;
}
const image = body.image ?? body.image_url;
if (typeof image === "string" && image) {
payload.image = image;
}
if (typeof body.seed === "number" && Number.isFinite(body.seed)) {
payload.seed = body.seed;
}
return payload;
}
/** Extracts a human-readable error message from a DeepInfra error/inference_status payload. */
/* @testonly */ export function extractDeepinfraErrorMessage(data: unknown): string | null {
if (!isRecord(data)) return null;
const direct = data.error ?? data.detail ?? data.message;
if (typeof direct === "string" && direct) return direct;
if (isRecord(direct) && typeof direct.message === "string" && direct.message) {
return direct.message;
}
const status = data.inference_status;
if (isRecord(status) && typeof status.error === "string" && status.error) {
return status.error;
}
return null;
}
interface CallLogContext {
provider: string;
model: string;
startTime: number;
}
function logDeepinfraCall(
ctx: CallLogContext,
status: number,
extra: { error?: string; responseBody?: Record<string, unknown> }
) {
saveCallLog({
method: "POST",
path: "/v1/videos/generations",
status,
model: `${ctx.provider}/${ctx.model}`,
provider: ctx.provider,
duration: Date.now() - ctx.startTime,
...extra,
}).catch(() => {});
}
function buildDeepinfraFetchError(
ctx: CallLogContext,
status: number,
data: unknown,
log?: DeepinfraHandlerArgs["log"]
) {
const errorMessage = extractDeepinfraErrorMessage(data) || `DeepInfra returned HTTP ${status}`;
log?.error?.("VIDEO", `${ctx.provider} deepinfra-video error ${status}: ${errorMessage}`);
logDeepinfraCall(ctx, status, { error: errorMessage.slice(0, 500) });
return { success: false, status, error: errorMessage };
}
function buildDeepinfraSuccess(ctx: CallLogContext, videoUrl: string) {
logDeepinfraCall(ctx, 200, { responseBody: { videos_count: 1 } });
return {
success: true,
data: {
created: Math.floor(Date.now() / 1000),
data: [{ url: videoUrl, format: "mp4" }],
},
};
}
function buildDeepinfraCatchError(
ctx: CallLogContext,
err: unknown,
log?: DeepinfraHandlerArgs["log"]
) {
const errorMessage = sanitizeErrorMessage(err) || "Video provider error";
log?.error?.("VIDEO", `${ctx.provider} deepinfra-video error: ${errorMessage}`);
logDeepinfraCall(ctx, 502, { error: errorMessage });
return { success: false, status: 502, error: errorMessage };
}
async function fetchDeepinfraVideo(
baseUrl: string,
model: string,
token: string,
requestBody: unknown
) {
const res = await fetch(`${baseUrl}/${model}`, {
method: "POST",
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
body: JSON.stringify(requestBody),
});
const data: DeepinfraVideoResponse = await res.json().catch(() => ({}));
return { res, data };
}
export async function handleDeepinfraVideoGeneration({
model,
provider,
providerConfig,
body,
credentials,
log,
}: DeepinfraHandlerArgs) {
const ctx: CallLogContext = { provider, model, startTime: Date.now() };
const token = credentials?.apiKey || credentials?.accessToken;
if (!token) {
return { success: false, status: 401, error: "DeepInfra API key is required" };
}
const baseUrl = providerConfig.baseUrl.replace(/\/$/, "");
const requestBody = buildDeepinfraVideoRequestBody(body);
const promptPreview = String(body.prompt ?? "").slice(0, 60);
log?.info?.("VIDEO", `${provider}/${model} (deepinfra-video) | prompt: "${promptPreview}..."`);
try {
const { res, data } = await fetchDeepinfraVideo(baseUrl, model, token, requestBody);
if (!res.ok) return buildDeepinfraFetchError(ctx, res.status, data, log);
const videoUrl = typeof data.video_url === "string" ? data.video_url : null;
if (!videoUrl) {
const errorMessage =
extractDeepinfraErrorMessage(data) || "DeepInfra video generation did not return video_url";
return { success: false, status: 502, error: errorMessage };
}
return buildDeepinfraSuccess(ctx, videoUrl);
} catch (err: unknown) {
return buildDeepinfraCatchError(ctx, err, log);
}
}

View File

@@ -0,0 +1,116 @@
/**
* Leonardo AI (Phoenix) video generation: submit → poll → fetch output.
*
* Extracted out of the frozen `videoGeneration.ts` god-file (unchanged behavior) to make
* room for the new DeepInfra video adapter without pushing the file-size ratchet over its
* baseline — mirrors the existing `googleFlowHandler.ts` extraction.
*/
import { saveCallLog } from "@/lib/usageDb";
function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
export async function handleLeonardoVideoGeneration({
model,
provider,
providerConfig,
body,
credentials,
log,
}) {
const startTime = Date.now();
const token = credentials?.apiKey || "";
const res = await fetch(providerConfig.baseUrl, {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
body: JSON.stringify({
modelId: "phoenix",
prompt: body.prompt,
width: 1024,
height: 576,
num_frames: 24,
}),
});
if (!res.ok) {
const errorText = await res.text();
saveCallLog({
method: "POST",
path: "/v1/videos/generations",
status: res.status,
model: `${provider}/${model}`,
provider,
duration: Date.now() - startTime,
error: errorText.slice(0, 500),
}).catch(() => {});
return { success: false, status: res.status, error: errorText };
}
const { sdGenerationJob } = await res.json();
const genId = sdGenerationJob?.generationId;
if (!genId) {
saveCallLog({
method: "POST",
path: "/v1/videos/generations",
status: 502,
model: `${provider}/${model}`,
provider,
duration: Date.now() - startTime,
error: "No generation ID returned",
}).catch(() => {});
return { success: false, status: 502, error: "No generation ID returned" };
}
const deadline = Date.now() + 300000;
while (Date.now() < deadline) {
await sleep(5000);
const statusRes = await fetch(`${providerConfig.baseUrl}/${genId}`, {
headers: { Authorization: `Bearer ${token}` },
});
const status = await statusRes.json();
const gen = status.generations_by_pk || status;
if (gen.status === "COMPLETE") {
const imgUrl = gen.generated_images?.[0]?.url;
if (imgUrl) {
const videoRes = await fetch(imgUrl);
const buf = await videoRes.arrayBuffer();
saveCallLog({
method: "POST",
path: "/v1/videos/generations",
status: 200,
model: `${provider}/${model}`,
provider,
duration: Date.now() - startTime,
}).catch(() => {});
return {
success: true,
data: {
created: Math.floor(Date.now() / 1000),
data: [{ b64_json: Buffer.from(buf).toString("base64"), format: "mp4" }],
},
};
}
}
if (gen.status === "FAILED") {
saveCallLog({
method: "POST",
path: "/v1/videos/generations",
status: 502,
model: `${provider}/${model}`,
provider,
duration: Date.now() - startTime,
error: "Leonardo video generation failed",
}).catch(() => {});
return { success: false, status: 502, error: "Leonardo video generation failed" };
}
}
saveCallLog({
method: "POST",
path: "/v1/videos/generations",
status: 504,
model: `${provider}/${model}`,
provider,
duration: Date.now() - startTime,
error: "Leonardo video generation timed out",
}).catch(() => {});
return { success: false, status: 504, error: "Leonardo video generation timed out" };
}

View File

@@ -0,0 +1,196 @@
import test from "node:test";
import assert from "node:assert/strict";
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
process.env.DATA_DIR = mkdtempSync(join(tmpdir(), "omniroute-video-deepinfra-"));
const { handleVideoGeneration } = await import("../../open-sse/handlers/videoGeneration.ts");
const { VIDEO_PROVIDERS, parseVideoModel } = await import(
"../../open-sse/config/videoRegistry.ts"
);
const {
buildDeepinfraVideoRequestBody,
extractDeepinfraErrorMessage,
} = await import("../../open-sse/handlers/videoGeneration/deepinfraHandler.ts");
const INFERENCE_URL = "https://api.deepinfra.com/v1/inference/Wan-AI/Wan2.2-T2V-A14B";
function jsonResponse(body, status = 200) {
return new Response(JSON.stringify(body), {
status,
headers: { "content-type": "application/json" },
});
}
test("VIDEO_PROVIDERS exposes the deepinfra deepinfra-video entry", () => {
assert.ok(VIDEO_PROVIDERS.deepinfra, "deepinfra video provider is registered");
assert.equal(VIDEO_PROVIDERS.deepinfra.format, "deepinfra-video");
assert.equal(VIDEO_PROVIDERS.deepinfra.authType, "apikey");
assert.equal(VIDEO_PROVIDERS.deepinfra.authHeader, "bearer");
assert.equal(VIDEO_PROVIDERS.deepinfra.baseUrl, "https://api.deepinfra.com/v1/inference");
assert.ok(
VIDEO_PROVIDERS.deepinfra.models.some((m) => m.id === "Wan-AI/Wan2.2-T2V-A14B"),
"Wan 2.2 T2V A14B is listed"
);
});
test("parseVideoModel resolves provider/model strings whose model id itself contains a slash", () => {
const parsed = parseVideoModel("deepinfra/Wan-AI/Wan2.2-T2V-A14B");
assert.equal(parsed.provider, "deepinfra");
assert.equal(parsed.model, "Wan-AI/Wan2.2-T2V-A14B");
});
test("buildDeepinfraVideoRequestBody carries prompt/negative_prompt/image/seed", () => {
const body = buildDeepinfraVideoRequestBody({
prompt: "a neon city in the rain",
negative_prompt: "blurry",
image: "https://example.com/frame.png",
seed: 42,
});
assert.deepEqual(body, {
prompt: "a neon city in the rain",
negative_prompt: "blurry",
image: "https://example.com/frame.png",
seed: 42,
});
});
test("buildDeepinfraVideoRequestBody omits optional fields when absent", () => {
const body = buildDeepinfraVideoRequestBody({ prompt: "just a prompt" });
assert.deepEqual(body, { prompt: "just a prompt" });
});
test("extractDeepinfraErrorMessage reads string error/detail/message and inference_status.error", () => {
assert.equal(extractDeepinfraErrorMessage({ error: "bad request" }), "bad request");
assert.equal(extractDeepinfraErrorMessage({ detail: "invalid model" }), "invalid model");
assert.equal(
extractDeepinfraErrorMessage({ error: { message: "nested" } }),
"nested"
);
assert.equal(
extractDeepinfraErrorMessage({ inference_status: { error: "queue timeout" } }),
"queue timeout"
);
assert.equal(extractDeepinfraErrorMessage({}), null);
assert.equal(extractDeepinfraErrorMessage(null), null);
});
test("handleVideoGeneration builds a synchronous DeepInfra request and returns the mp4 url", async () => {
const originalFetch = globalThis.fetch;
let capturedRequest;
globalThis.fetch = async (url, options = {}) => {
capturedRequest = {
url: String(url),
headers: options.headers,
body: JSON.parse(String(options.body || "{}")),
};
return jsonResponse({
video_url: "https://deepinfra-cdn.example.com/wan-out.mp4",
seed: 7,
request_id: "req-1",
inference_status: { status: "succeeded" },
});
};
try {
const result = await handleVideoGeneration({
body: {
model: "deepinfra/Wan-AI/Wan2.2-T2V-A14B",
prompt: "a neon city in the rain",
negative_prompt: "blurry",
},
credentials: { apiKey: "deepinfra-key" },
log: null,
});
assert.equal(capturedRequest.url, INFERENCE_URL);
assert.equal(capturedRequest.headers["Authorization"], "Bearer deepinfra-key");
assert.equal(capturedRequest.body.prompt, "a neon city in the rain");
assert.equal(capturedRequest.body.negative_prompt, "blurry");
assert.equal(result.success, true);
assert.equal(result.data.data[0].url, "https://deepinfra-cdn.example.com/wan-out.mp4");
assert.equal(result.data.data[0].format, "mp4");
} finally {
globalThis.fetch = originalFetch;
}
});
test("handleVideoGeneration rejects DeepInfra video requests without credentials", async () => {
const result = await handleVideoGeneration({
body: { model: "deepinfra/Wan-AI/Wan2.2-T2V-A14B", prompt: "x" },
credentials: null,
log: null,
});
assert.equal(result.success, false);
assert.equal(result.status, 401);
assert.match(result.error, /DeepInfra API key is required/);
});
test("handleVideoGeneration surfaces upstream HTTP errors without leaking a stack trace", async () => {
const originalFetch = globalThis.fetch;
globalThis.fetch = async () =>
jsonResponse({ error: "Invalid API key" }, 401);
try {
const result = await handleVideoGeneration({
body: { model: "deepinfra/Wan-AI/Wan2.2-T2V-A14B", prompt: "x" },
credentials: { apiKey: "bad-key" },
log: null,
});
assert.equal(result.success, false);
assert.equal(result.status, 401);
assert.equal(result.error, "Invalid API key");
assert.ok(!result.error.includes("at /"), "error must not leak a stack trace");
} finally {
globalThis.fetch = originalFetch;
}
});
test("handleVideoGeneration returns 502 when DeepInfra succeeds without a video_url", async () => {
const originalFetch = globalThis.fetch;
globalThis.fetch = async () => jsonResponse({ seed: 1, request_id: "req-2" });
try {
const result = await handleVideoGeneration({
body: { model: "deepinfra/Wan-AI/Wan2.2-T2V-A14B", prompt: "x" },
credentials: { apiKey: "deepinfra-key" },
log: null,
});
assert.equal(result.success, false);
assert.equal(result.status, 502);
assert.match(result.error, /did not return video_url/);
} finally {
globalThis.fetch = originalFetch;
}
});
test("handleVideoGeneration sanitizes network-level failures via sanitizeErrorMessage", async () => {
const originalFetch = globalThis.fetch;
globalThis.fetch = async () => {
throw new Error(
"fetch failed\n at Object.fetch (/home/user/omniroute/node_modules/undici/lib/x.js:1:1)"
);
};
try {
const result = await handleVideoGeneration({
body: { model: "deepinfra/Wan-AI/Wan2.2-T2V-A14B", prompt: "x" },
credentials: { apiKey: "deepinfra-key" },
log: null,
});
assert.equal(result.success, false);
assert.equal(result.status, 502);
assert.ok(!result.error.includes("at Object.fetch"), "error must not leak a stack trace");
assert.ok(!result.error.includes("/home/user"), "error must not leak an absolute path");
} finally {
globalThis.fetch = originalFetch;
}
});