CODE · 2.5 KB
server/fal-client.js
Workspace snapshot · 09/04 14:52
import { execFile } from "node:child_process";
import { promisify } from "node:util";
const execFileAsync = promisify(execFile);
const credentialTarget = "AgentVentureStudio:Fal:AiVideoLab";
const credentialReader = "C:\\service\\agent-venture-studio\\scripts\\read-avs-credential.ps1";
async function readStoredFalKey() {
try {
const { stdout } = await execFileAsync(
"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
["-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", credentialReader, "-Target", credentialTarget],
{ timeout: 30000, maxBuffer: 4096, windowsHide: true, encoding: "utf8" }
);
return stdout.trim();
} catch {
return "";
}
}
export async function isFalConfigured(env = process.env) {
return Boolean(env.FAL_KEY || await readStoredFalKey());
}
export async function createFalClient(env = process.env) {
const credentials = env.FAL_KEY || await readStoredFalKey();
if (!credentials) throw new Error("FAL_KEY is required for this provider");
const { fal } = await import("@fal-ai/client");
fal.config({ credentials });
return fal;
}
export function buildFalInput(provider, input) {
const common = { prompt: input.prompt, aspect_ratio: input.aspectRatio, duration: input.durationSeconds };
switch (provider.requestShape) {
case "omni": return common;
case "flux": return { ...common, resolution: provider.resolution, generate_audio: false, safety_tolerance: 1 };
case "seedance": return { ...common, duration: String(input.durationSeconds), resolution: provider.resolution, generate_audio: false, bitrate_mode: "standard" };
case "minimax": return { ...common, resolution: provider.resolution };
case "kling": return { ...common, generate_audio: false };
default: throw new Error(`unsupported fal request shape: ${provider.requestShape}`);
}
}
export async function runFalGeneration(input, { provider, client, onProgress = () => {} }) {
onProgress({ state: "RUNNING", engine: "fal", model: provider.model });
const result = await client.subscribe(provider.model, {
input: buildFalInput(provider, input),
logs: true,
onQueueUpdate(update) {
onProgress({ state: update.status || "RUNNING", engine: "fal", model: provider.model });
}
});
const data = result.data || result;
const video = data.video || data.videos?.[0];
if (!video?.url) throw new Error("fal completed without a video URL");
return { uri: video.url, videoBytes: null, model: provider.model, engine: "fal", requestId: result.requestId || null };
}