type AliyunVideoModel = "qwen3.8-max" | "qwen3.8-flash" | "qwen3.8-omni-flash"; export function isAliyunVideoModel(modelId: string, providerType: string) { return providerType === "alibaba" && ["qwen3.8-max", "qwen3.8-flash", "qwen3.8-omni-flash"].includes(modelId); } export function supportsVideoSound(modelId: string) { return modelId === "qwen3.8-omni-flash"; } function officialEndpoint(baseUrl: string) { const url = new URL(baseUrl); if (url.protocol !== "https:" || !(url.hostname === "dashscope.aliyuncs.com" || /^[-a-z0-9]+\.[-a-z0-9]+\.maas\.aliyuncs\.com$/.test(url.hostname))) { throw new Error("视频解析仅允许阿里云百炼官方地址"); } return `${url.href.replace(/\/$/, "")}/chat/completions`; } function sseText(body: string) { let result = ""; let finishReason: string | undefined; for (const line of body.split(/\r?\n/)) { if (!line.startsWith("data: ") || line === "data: [DONE]") continue; try { const event = JSON.parse(line.slice(6)); result += event.choices?.[0]?.delta?.content || ""; finishReason = event.choices?.[0]?.finish_reason || finishReason; } catch { // Ignore keep-alive and incomplete events; a missing result is rejected below. } } if (finishReason === "length") throw new Error("阿里云视频分析结果被截断,未提交成片合成"); return result; } export async function analyzeAliyunVideo({ apiKey, baseUrl, modelId, prompt, videoUrl, contextImages = [], fps = 1, jsonOutput = false, signal, }: { apiKey: string; baseUrl: string; modelId: AliyunVideoModel; prompt: string; videoUrl: string; contextImages?: string[]; fps?: number; jsonOutput?: boolean; signal?: AbortSignal; }) { const video = new URL(videoUrl); if (video.protocol !== "https:") { throw new Error("阿里云视频解析需要可公网访问的 HTTPS 视频地址"); } if (!Number.isFinite(fps) || fps < 0.1 || fps > 10 || contextImages.length > 2) throw new Error("视频分析采样或参考图片参数无效"); const omni = supportsVideoSound(modelId); const response = await fetch(officialEndpoint(baseUrl), { method: "POST", headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json", }, body: JSON.stringify({ model: modelId, messages: [{ role: "user", content: [ { type: "video_url", video_url: { url: video.href }, fps }, ...contextImages.map(url => ({ type: "image_url", image_url: { url } })), { type: "text", text: prompt }, ], }], stream: omni, ...(jsonOutput ? { response_format: { type: "json_object" } } : {}), ...(omni ? { modalities: ["text"], reasoning_effort: "none" } : { temperature: 0.2, max_tokens: 6000, }), }), signal, }); if (!response.ok) { let code = ""; try { const body = await response.json(); code = String(body.error?.code || body.code || "").slice(0, 80); } catch {} throw new Error(`阿里云视频解析失败(HTTP ${response.status}${code ? `,${code}` : ""})`); } let text: string; if (omni) text = sseText(await response.text()); else { const choice = (await response.json()).choices?.[0]; if (choice?.finish_reason === "length") throw new Error("阿里云视频分析结果被截断,未提交成片合成"); text = String(choice?.message?.content || ""); } if (!text.trim()) throw new Error("阿里云视频解析返回空结果"); return text; }