mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-01 21:02:12 +03:00
fix: address code review issues (SSRF, saveCallLog, deduplication)
- Add path traversal validation for ElevenLabs voice_id and HuggingFace model_id URL concatenation (prevents SSRF via ../ sequences) - Add saveCallLog usage tracking to video and music handlers for consistent analytics with imageGeneration.ts - Extract shared upstreamErrorResponse() and audioStreamResponse() helpers to reduce error handling duplication in audioSpeech.ts - Extract shared upstreamErrorResponse() and isValidPathSegment() helpers in audioTranscription.ts - Add explicit format: "openai" to qwen TTS and STT provider entries - Remove unused modelId parameter from handleCoquiSpeech and handleTortoiseSpeech - Filter cloud video/music providers by active status in models route (local providers with authType: "none" always listed) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -99,6 +99,7 @@ export const AUDIO_TRANSCRIPTION_PROVIDERS: Record<string, AudioProvider> = {
|
||||
baseUrl: "http://localhost:8000/v1/audio/transcriptions",
|
||||
authType: "none",
|
||||
authHeader: "none",
|
||||
format: "openai",
|
||||
models: [
|
||||
{ id: "qwen3-asr", name: "Qwen3 ASR" },
|
||||
],
|
||||
@@ -203,6 +204,7 @@ export const AUDIO_SPEECH_PROVIDERS: Record<string, AudioProvider> = {
|
||||
baseUrl: "http://localhost:8000/v1/audio/speech",
|
||||
authType: "none",
|
||||
authHeader: "none",
|
||||
format: "openai",
|
||||
models: [
|
||||
{ id: "qwen3-tts", name: "Qwen3 TTS" },
|
||||
],
|
||||
|
||||
@@ -20,6 +20,42 @@ import { getSpeechProvider, parseSpeechModel } from "../config/audioRegistry.ts"
|
||||
import { buildAuthHeaders } from "../config/registryUtils.ts";
|
||||
import { errorResponse } from "../utils/error.ts";
|
||||
|
||||
/**
|
||||
* Return a CORS error response from an upstream fetch failure
|
||||
*/
|
||||
function upstreamErrorResponse(res, errText) {
|
||||
return new Response(errText, {
|
||||
status: res.status,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Access-Control-Allow-Origin": getCorsOrigin(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a CORS audio stream response
|
||||
*/
|
||||
function audioStreamResponse(res, defaultContentType = "audio/mpeg") {
|
||||
const contentType = res.headers.get("content-type") || defaultContentType;
|
||||
return new Response(res.body, {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": contentType,
|
||||
"Access-Control-Allow-Origin": getCorsOrigin(),
|
||||
"Transfer-Encoding": "chunked",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a path segment to prevent path traversal / SSRF.
|
||||
* Returns true if safe, false if it contains traversal sequences.
|
||||
*/
|
||||
function isValidPathSegment(segment: string): boolean {
|
||||
return !segment.includes("..") && !segment.includes("//");
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Hyperbolic TTS (returns base64 audio in JSON)
|
||||
*/
|
||||
@@ -34,14 +70,7 @@ async function handleHyperbolicSpeech(providerConfig, body, token) {
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const errText = await res.text();
|
||||
return new Response(errText, {
|
||||
status: res.status,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Access-Control-Allow-Origin": getCorsOrigin(),
|
||||
},
|
||||
});
|
||||
return upstreamErrorResponse(res, await res.text());
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
@@ -74,25 +103,10 @@ async function handleDeepgramSpeech(providerConfig, body, modelId, token) {
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const errText = await res.text();
|
||||
return new Response(errText, {
|
||||
status: res.status,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Access-Control-Allow-Origin": getCorsOrigin(),
|
||||
},
|
||||
});
|
||||
return upstreamErrorResponse(res, await res.text());
|
||||
}
|
||||
|
||||
const contentType = res.headers.get("content-type") || "audio/mpeg";
|
||||
return new Response(res.body, {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": contentType,
|
||||
"Access-Control-Allow-Origin": getCorsOrigin(),
|
||||
"Transfer-Encoding": "chunked",
|
||||
},
|
||||
});
|
||||
return audioStreamResponse(res);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,6 +117,9 @@ async function handleDeepgramSpeech(providerConfig, body, modelId, token) {
|
||||
async function handleElevenLabsSpeech(providerConfig, body, modelId, token) {
|
||||
// ElevenLabs uses voice_id in URL path; default to "21m00Tcm4TlvDq8ikWAM" (Rachel)
|
||||
const voiceId = body.voice || "21m00Tcm4TlvDq8ikWAM";
|
||||
if (!isValidPathSegment(voiceId)) {
|
||||
return errorResponse(400, "Invalid voice ID");
|
||||
}
|
||||
const url = `${providerConfig.baseUrl}/${voiceId}`;
|
||||
|
||||
const res = await fetch(url, {
|
||||
@@ -118,25 +135,10 @@ async function handleElevenLabsSpeech(providerConfig, body, modelId, token) {
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const errText = await res.text();
|
||||
return new Response(errText, {
|
||||
status: res.status,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Access-Control-Allow-Origin": getCorsOrigin(),
|
||||
},
|
||||
});
|
||||
return upstreamErrorResponse(res, await res.text());
|
||||
}
|
||||
|
||||
const contentType = res.headers.get("content-type") || "audio/mpeg";
|
||||
return new Response(res.body, {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": contentType,
|
||||
"Access-Control-Allow-Origin": getCorsOrigin(),
|
||||
"Transfer-Encoding": "chunked",
|
||||
},
|
||||
});
|
||||
return audioStreamResponse(res);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,25 +160,10 @@ async function handleNvidiaTtsSpeech(providerConfig, body, modelId, token) {
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const errText = await res.text();
|
||||
return new Response(errText, {
|
||||
status: res.status,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Access-Control-Allow-Origin": getCorsOrigin(),
|
||||
},
|
||||
});
|
||||
return upstreamErrorResponse(res, await res.text());
|
||||
}
|
||||
|
||||
const contentType = res.headers.get("content-type") || "audio/wav";
|
||||
return new Response(res.body, {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": contentType,
|
||||
"Access-Control-Allow-Origin": getCorsOrigin(),
|
||||
"Transfer-Encoding": "chunked",
|
||||
},
|
||||
});
|
||||
return audioStreamResponse(res, "audio/wav");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,6 +171,9 @@ async function handleNvidiaTtsSpeech(providerConfig, body, modelId, token) {
|
||||
* POST {baseUrl}/{model_id} with { inputs: text } → audio binary
|
||||
*/
|
||||
async function handleHuggingFaceTtsSpeech(providerConfig, body, modelId, token) {
|
||||
if (!isValidPathSegment(modelId)) {
|
||||
return errorResponse(400, "Invalid model ID");
|
||||
}
|
||||
const url = `${providerConfig.baseUrl}/${modelId}`;
|
||||
|
||||
const res = await fetch(url, {
|
||||
@@ -196,32 +186,17 @@ async function handleHuggingFaceTtsSpeech(providerConfig, body, modelId, token)
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const errText = await res.text();
|
||||
return new Response(errText, {
|
||||
status: res.status,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Access-Control-Allow-Origin": getCorsOrigin(),
|
||||
},
|
||||
});
|
||||
return upstreamErrorResponse(res, await res.text());
|
||||
}
|
||||
|
||||
const contentType = res.headers.get("content-type") || "audio/wav";
|
||||
return new Response(res.body, {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": contentType,
|
||||
"Access-Control-Allow-Origin": getCorsOrigin(),
|
||||
"Transfer-Encoding": "chunked",
|
||||
},
|
||||
});
|
||||
return audioStreamResponse(res, "audio/wav");
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Coqui TTS (local, no auth)
|
||||
* POST {baseUrl} with { text, speaker_id } → WAV audio
|
||||
*/
|
||||
async function handleCoquiSpeech(providerConfig, body, modelId) {
|
||||
async function handleCoquiSpeech(providerConfig, body) {
|
||||
const res = await fetch(providerConfig.baseUrl, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
@@ -232,14 +207,7 @@ async function handleCoquiSpeech(providerConfig, body, modelId) {
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const errText = await res.text();
|
||||
return new Response(errText, {
|
||||
status: res.status,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Access-Control-Allow-Origin": getCorsOrigin(),
|
||||
},
|
||||
});
|
||||
return upstreamErrorResponse(res, await res.text());
|
||||
}
|
||||
|
||||
const contentType = res.headers.get("content-type") || "audio/wav";
|
||||
@@ -256,7 +224,7 @@ async function handleCoquiSpeech(providerConfig, body, modelId) {
|
||||
* Handle Tortoise TTS (local, no auth)
|
||||
* POST {baseUrl} with { text, voice } → audio binary
|
||||
*/
|
||||
async function handleTortoiseSpeech(providerConfig, body, modelId) {
|
||||
async function handleTortoiseSpeech(providerConfig, body) {
|
||||
const res = await fetch(providerConfig.baseUrl, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
@@ -267,14 +235,7 @@ async function handleTortoiseSpeech(providerConfig, body, modelId) {
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const errText = await res.text();
|
||||
return new Response(errText, {
|
||||
status: res.status,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Access-Control-Allow-Origin": getCorsOrigin(),
|
||||
},
|
||||
});
|
||||
return upstreamErrorResponse(res, await res.text());
|
||||
}
|
||||
|
||||
const contentType = res.headers.get("content-type") || "audio/wav";
|
||||
@@ -343,11 +304,11 @@ export async function handleAudioSpeech({ body, credentials }) {
|
||||
}
|
||||
|
||||
if (providerConfig.format === "coqui") {
|
||||
return handleCoquiSpeech(providerConfig, body, modelId);
|
||||
return handleCoquiSpeech(providerConfig, body);
|
||||
}
|
||||
|
||||
if (providerConfig.format === "tortoise") {
|
||||
return handleTortoiseSpeech(providerConfig, body, modelId);
|
||||
return handleTortoiseSpeech(providerConfig, body);
|
||||
}
|
||||
|
||||
// Default: OpenAI-compatible JSON → audio stream proxy (also used by Qwen3)
|
||||
@@ -367,26 +328,10 @@ export async function handleAudioSpeech({ body, credentials }) {
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const errText = await res.text();
|
||||
return new Response(errText, {
|
||||
status: res.status,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Access-Control-Allow-Origin": getCorsOrigin(),
|
||||
},
|
||||
});
|
||||
return upstreamErrorResponse(res, await res.text());
|
||||
}
|
||||
|
||||
// Stream audio response back to client
|
||||
const contentType = res.headers.get("content-type") || "audio/mpeg";
|
||||
return new Response(res.body, {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": contentType,
|
||||
"Access-Control-Allow-Origin": getCorsOrigin(),
|
||||
"Transfer-Encoding": "chunked",
|
||||
},
|
||||
});
|
||||
return audioStreamResponse(res);
|
||||
} catch (err) {
|
||||
return errorResponse(500, `Speech request failed: ${err.message}`);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,26 @@ import { getTranscriptionProvider, parseTranscriptionModel } from "../config/aud
|
||||
import { buildAuthHeaders } from "../config/registryUtils.ts";
|
||||
import { errorResponse } from "../utils/error.ts";
|
||||
|
||||
/**
|
||||
* Return a CORS error response from an upstream fetch failure
|
||||
*/
|
||||
function upstreamErrorResponse(res, errText) {
|
||||
return new Response(errText, {
|
||||
status: res.status,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Access-Control-Allow-Origin": getCorsOrigin(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a path segment to prevent path traversal / SSRF.
|
||||
*/
|
||||
function isValidPathSegment(segment: string): boolean {
|
||||
return !segment.includes("..") && !segment.includes("//");
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Deepgram transcription (raw binary audio, model via query param)
|
||||
*/
|
||||
@@ -37,14 +57,7 @@ async function handleDeepgramTranscription(providerConfig, file, modelId, token)
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const errText = await res.text();
|
||||
return new Response(errText, {
|
||||
status: res.status,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Access-Control-Allow-Origin": getCorsOrigin(),
|
||||
},
|
||||
});
|
||||
return upstreamErrorResponse(res, await res.text());
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
@@ -72,14 +85,7 @@ async function handleAssemblyAITranscription(providerConfig, file, modelId, toke
|
||||
});
|
||||
|
||||
if (!uploadRes.ok) {
|
||||
const errText = await uploadRes.text();
|
||||
return new Response(errText, {
|
||||
status: uploadRes.status,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Access-Control-Allow-Origin": getCorsOrigin(),
|
||||
},
|
||||
});
|
||||
return upstreamErrorResponse(uploadRes, await uploadRes.text());
|
||||
}
|
||||
|
||||
const { upload_url } = await uploadRes.json();
|
||||
@@ -99,14 +105,7 @@ async function handleAssemblyAITranscription(providerConfig, file, modelId, toke
|
||||
});
|
||||
|
||||
if (!submitRes.ok) {
|
||||
const errText = await submitRes.text();
|
||||
return new Response(errText, {
|
||||
status: submitRes.status,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Access-Control-Allow-Origin": getCorsOrigin(),
|
||||
},
|
||||
});
|
||||
return upstreamErrorResponse(submitRes, await submitRes.text());
|
||||
}
|
||||
|
||||
const { id: transcriptId } = await submitRes.json();
|
||||
@@ -155,14 +154,7 @@ async function handleNvidiaTranscription(providerConfig, file, modelId, token) {
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const errText = await res.text();
|
||||
return new Response(errText, {
|
||||
status: res.status,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Access-Control-Allow-Origin": getCorsOrigin(),
|
||||
},
|
||||
});
|
||||
return upstreamErrorResponse(res, await res.text());
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
@@ -177,6 +169,9 @@ async function handleNvidiaTranscription(providerConfig, file, modelId, token) {
|
||||
* POST raw binary audio to {baseUrl}/{model_id}, returns { text }
|
||||
*/
|
||||
async function handleHuggingFaceTranscription(providerConfig, file, modelId, token) {
|
||||
if (!isValidPathSegment(modelId)) {
|
||||
return errorResponse(400, "Invalid model ID");
|
||||
}
|
||||
const url = `${providerConfig.baseUrl}/${modelId}`;
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
|
||||
@@ -190,14 +185,7 @@ async function handleHuggingFaceTranscription(providerConfig, file, modelId, tok
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const errText = await res.text();
|
||||
return new Response(errText, {
|
||||
status: res.status,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Access-Control-Allow-Origin": getCorsOrigin(),
|
||||
},
|
||||
});
|
||||
return upstreamErrorResponse(res, await res.text());
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
@@ -291,14 +279,7 @@ export async function handleAudioTranscription({ formData, credentials }) {
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const errText = await res.text();
|
||||
return new Response(errText, {
|
||||
status: res.status,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Access-Control-Allow-Origin": getCorsOrigin(),
|
||||
},
|
||||
});
|
||||
return upstreamErrorResponse(res, await res.text());
|
||||
}
|
||||
|
||||
const data = await res.text();
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
fetchComfyOutput,
|
||||
extractComfyOutputFiles,
|
||||
} from "../utils/comfyuiClient.ts";
|
||||
import { saveCallLog } from "@/lib/usageDb";
|
||||
|
||||
/**
|
||||
* Handle music generation request
|
||||
@@ -57,6 +58,7 @@ export async function handleMusicGeneration({ body, credentials, log }) {
|
||||
* Submits an audio generation workflow (Stable Audio / MusicGen), polls, fetches output
|
||||
*/
|
||||
async function handleComfyUIMusicGeneration({ model, provider, providerConfig, body, log }) {
|
||||
const startTime = Date.now();
|
||||
const duration = body.duration || 10; // seconds
|
||||
|
||||
// Audio generation workflow template for ComfyUI
|
||||
@@ -127,12 +129,31 @@ async function handleComfyUIMusicGeneration({ model, provider, providerConfig, b
|
||||
audioFiles.push({ b64_json: base64, format: "wav" });
|
||||
}
|
||||
|
||||
saveCallLog({
|
||||
method: "POST",
|
||||
path: "/v1/music/generations",
|
||||
status: 200,
|
||||
model: `${provider}/${model}`,
|
||||
provider,
|
||||
duration: Date.now() - startTime,
|
||||
responseBody: { audio_count: audioFiles.length },
|
||||
}).catch(() => {});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: { created: Math.floor(Date.now() / 1000), data: audioFiles },
|
||||
};
|
||||
} catch (err) {
|
||||
if (log) log.error("MUSIC", `${provider} comfyui error: ${err.message}`);
|
||||
saveCallLog({
|
||||
method: "POST",
|
||||
path: "/v1/music/generations",
|
||||
status: 502,
|
||||
model: `${provider}/${model}`,
|
||||
provider,
|
||||
duration: Date.now() - startTime,
|
||||
error: err.message,
|
||||
}).catch(() => {});
|
||||
return { success: false, status: 502, error: `Music provider error: ${err.message}` };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
fetchComfyOutput,
|
||||
extractComfyOutputFiles,
|
||||
} from "../utils/comfyuiClient.ts";
|
||||
import { saveCallLog } from "@/lib/usageDb";
|
||||
|
||||
/**
|
||||
* Handle video generation request
|
||||
@@ -62,6 +63,7 @@ export async function handleVideoGeneration({ body, credentials, log }) {
|
||||
* Submits an AnimateDiff or SVD workflow, polls for completion, fetches output video
|
||||
*/
|
||||
async function handleComfyUIVideoGeneration({ model, provider, providerConfig, body, log }) {
|
||||
const startTime = Date.now();
|
||||
const [width, height] = (body.size || "512x512").split("x").map(Number);
|
||||
const frames = body.frames || 16;
|
||||
|
||||
@@ -137,12 +139,31 @@ async function handleComfyUIVideoGeneration({ model, provider, providerConfig, b
|
||||
videos.push({ b64_json: base64, format: "webp" });
|
||||
}
|
||||
|
||||
saveCallLog({
|
||||
method: "POST",
|
||||
path: "/v1/videos/generations",
|
||||
status: 200,
|
||||
model: `${provider}/${model}`,
|
||||
provider,
|
||||
duration: Date.now() - startTime,
|
||||
responseBody: { videos_count: videos.length },
|
||||
}).catch(() => {});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: { created: Math.floor(Date.now() / 1000), data: videos },
|
||||
};
|
||||
} catch (err) {
|
||||
if (log) log.error("VIDEO", `${provider} comfyui error: ${err.message}`);
|
||||
saveCallLog({
|
||||
method: "POST",
|
||||
path: "/v1/videos/generations",
|
||||
status: 502,
|
||||
model: `${provider}/${model}`,
|
||||
provider,
|
||||
duration: Date.now() - startTime,
|
||||
error: err.message,
|
||||
}).catch(() => {});
|
||||
return { success: false, status: 502, error: `Video provider error: ${err.message}` };
|
||||
}
|
||||
}
|
||||
@@ -152,6 +173,7 @@ async function handleComfyUIVideoGeneration({ model, provider, providerConfig, b
|
||||
* POST to the AnimateDiff API endpoint
|
||||
*/
|
||||
async function handleSDWebUIVideoGeneration({ model, provider, providerConfig, body, log }) {
|
||||
const startTime = Date.now();
|
||||
const [width, height] = (body.size || "512x512").split("x").map(Number);
|
||||
const url = `${providerConfig.baseUrl}/animatediff/v1/generate`;
|
||||
|
||||
@@ -181,6 +203,15 @@ async function handleSDWebUIVideoGeneration({ model, provider, providerConfig, b
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
if (log) log.error("VIDEO", `${provider} error ${response.status}: ${errorText.slice(0, 200)}`);
|
||||
saveCallLog({
|
||||
method: "POST",
|
||||
path: "/v1/videos/generations",
|
||||
status: response.status,
|
||||
model: `${provider}/${model}`,
|
||||
provider,
|
||||
duration: Date.now() - startTime,
|
||||
error: errorText.slice(0, 500),
|
||||
}).catch(() => {});
|
||||
return { success: false, status: response.status, error: errorText };
|
||||
}
|
||||
|
||||
@@ -195,12 +226,31 @@ async function handleSDWebUIVideoGeneration({ model, provider, providerConfig, b
|
||||
}
|
||||
}
|
||||
|
||||
saveCallLog({
|
||||
method: "POST",
|
||||
path: "/v1/videos/generations",
|
||||
status: 200,
|
||||
model: `${provider}/${model}`,
|
||||
provider,
|
||||
duration: Date.now() - startTime,
|
||||
responseBody: { videos_count: videos.length },
|
||||
}).catch(() => {});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: { created: Math.floor(Date.now() / 1000), data: videos },
|
||||
};
|
||||
} catch (err) {
|
||||
if (log) log.error("VIDEO", `${provider} sdwebui error: ${err.message}`);
|
||||
saveCallLog({
|
||||
method: "POST",
|
||||
path: "/v1/videos/generations",
|
||||
status: 502,
|
||||
model: `${provider}/${model}`,
|
||||
provider,
|
||||
duration: Date.now() - startTime,
|
||||
error: err.message,
|
||||
}).catch(() => {});
|
||||
return { success: false, status: 502, error: `Video provider error: ${err.message}` };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ import { getAllImageModels } from "@omniroute/open-sse/config/imageRegistry.ts";
|
||||
import { getAllRerankModels } from "@omniroute/open-sse/config/rerankRegistry.ts";
|
||||
import { getAllAudioModels } from "@omniroute/open-sse/config/audioRegistry.ts";
|
||||
import { getAllModerationModels } from "@omniroute/open-sse/config/moderationRegistry.ts";
|
||||
import { getAllVideoModels } from "@omniroute/open-sse/config/videoRegistry.ts";
|
||||
import { getAllMusicModels } from "@omniroute/open-sse/config/musicRegistry.ts";
|
||||
import { getAllVideoModels, getVideoProvider } from "@omniroute/open-sse/config/videoRegistry.ts";
|
||||
import { getAllMusicModels, getMusicProvider } from "@omniroute/open-sse/config/musicRegistry.ts";
|
||||
|
||||
const FALLBACK_ALIAS_TO_PROVIDER = {
|
||||
ag: "antigravity",
|
||||
@@ -314,6 +314,8 @@ export async function GET(request: Request) {
|
||||
|
||||
// Add video models (local providers always listed, cloud filtered by active)
|
||||
for (const videoModel of getAllVideoModels()) {
|
||||
const vConfig = getVideoProvider(videoModel.provider);
|
||||
if (vConfig?.authType !== "none" && !isProviderActive(videoModel.provider)) continue;
|
||||
models.push({
|
||||
id: videoModel.id,
|
||||
object: "model",
|
||||
@@ -325,6 +327,8 @@ export async function GET(request: Request) {
|
||||
|
||||
// Add music models (local providers always listed, cloud filtered by active)
|
||||
for (const musicModel of getAllMusicModels()) {
|
||||
const mConfig = getMusicProvider(musicModel.provider);
|
||||
if (mConfig?.authType !== "none" && !isProviderActive(musicModel.provider)) continue;
|
||||
models.push({
|
||||
id: musicModel.id,
|
||||
object: "model",
|
||||
|
||||
Reference in New Issue
Block a user