fix(fal-video): sanitize credentials and urls for kling requests
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-02-19 01:50:15 +08:00
parent 5694fefca0
commit 825da7fd52

View File

@ -22,14 +22,34 @@ const DEFAULT_RATIO = "16:9";
const DEFAULT_SHOT_TYPE = "customize";
const DEFAULT_TIMEOUT_MS = 10 * 60 * 1000;
function normalizeFalTextInput(value: string) {
return String(value || "")
.replace(/\u00a0/g, " ")
.trim()
.normalize("NFKC");
}
function normalizeFalCredentials(apiKey: string) {
const normalized = apiKey.replace(/\u00a0/g, " ").trim();
const normalized = normalizeFalTextInput(apiKey);
const stripped = normalized.replace(/^bearer\s+/i, "").replace(/^key\s+/i, "");
const kvMatch = stripped.match(/^(?:fal[_-]?key|api[_-]?key|token)\s*[::]\s*(.+)$/i);
if (kvMatch?.[1]) {
return kvMatch[1].trim();
return kvMatch[1].trim().replace(/[^\x20-\x7E]/g, "");
}
return stripped.replace(/[^\x20-\x7E]/g, "");
}
function normalizeFalModelId(modelId: string) {
return normalizeFalTextInput(modelId);
}
function normalizeFalImageUrl(url: string) {
const normalized = normalizeFalTextInput(url);
try {
return new URL(normalized).toString();
} catch {
return normalized;
}
return stripped;
}
function normalizeDuration(value?: number) {
@ -60,7 +80,7 @@ function extractPromptAndImages(messages: Array<{ role: string; content: any }>)
const pushImageUrl = (url?: string) => {
if (!url) return;
const normalized = String(url).trim();
const normalized = normalizeFalImageUrl(String(url));
if (normalized.startsWith("data:")) {
throw new Error("FAL 视频模型不支持 Base64 图片,请先上传图片后再生成视频");
@ -212,7 +232,7 @@ export function createFalVideo({ apiKey, baseURL }: FalVideoConfig) {
overrideModelId?: string;
}) => {
try {
const activeModelId = options.overrideModelId || modelId;
const activeModelId = normalizeFalModelId(options.overrideModelId || modelId);
const { prompt, imageUrls } = extractPromptAndImages(options.messages);
const input = buildFalInput({
prompt,
@ -229,8 +249,12 @@ export function createFalVideo({ apiKey, baseURL }: FalVideoConfig) {
});
const fal = await import("@fal-ai/serverless-client");
const credentials = normalizeFalCredentials(apiKey);
if (!credentials) {
throw new Error("FAL API Key 未配置或格式无效");
}
fal.config({
credentials: normalizeFalCredentials(apiKey),
credentials,
});
const result: any = await fal.subscribe(activeModelId, {
@ -266,8 +290,12 @@ export function createFalVideo({ apiKey, baseURL }: FalVideoConfig) {
};
} catch (error) {
console.error("[Fal Video] Generation failed", error);
const errorText =
error instanceof Error ? `视频生成失败: ${error.message}` : "视频生成失败,请稍后重试";
const rawErrorText = error instanceof Error ? error.message : "";
const errorText = /ByteString/i.test(rawErrorText)
? "视频生成失败: 检测到配置中包含非法字符(常见为中文冒号),请检查 FAL API Key 与模型ID"
: error instanceof Error
? `视频生成失败: ${error.message}`
: "视频生成失败,请稍后重试";
return {
text: errorText,
finishReason: "error" as const,