UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

318 lines (317 loc) 13.3 kB
import { c as isRecord } from "../../record-coerce-DItp3I4t.js"; import { l as normalizeOptionalString } from "../../string-coerce-CIXf7egm.js"; import { m as readProviderJsonResponse, n as assertOkOrThrowHttpError } from "../../provider-http-errors-U-nhuk_f.js"; import { p as sanitizeConfiguredModelProviderRequest } from "../../provider-request-config-DIOYidiO.js"; import { n as resolveGeneratedMediaMaxBytes } from "../../configured-max-bytes-CTSUbFDK.js"; import { g as waitProviderOperationPollInterval, h as resolveProviderOperationTimeoutMs, i as createProviderOperationTimeoutResolver, l as postJsonRequest, p as resolveProviderHttpRequestConfig, r as createProviderOperationDeadline } from "../../shared-DCDpYrmy.js"; import "../../string-coerce-runtime-GQa0ehRA.js"; import "../../media-generation-runtime-Dd81xPqM.js"; import { a as resolveApiKeyForProvider } from "../../provider-auth-runtime-BqyR7mkf.js"; import "../../provider-http-k9RMI7iG.js"; import { d as toImageDataUrl } from "../../image-generation-B61kkjvL.js"; import { a as XAI_VIDEO_ASPECT_RATIOS, o as XAI_VIDEO_DEFAULT_TIMEOUT_MS, p as isXaiVideo15Model, t as DEFAULT_XAI_VIDEO_BASE_URL } from "../../capability-provider-metadata-factory-BjaSolS_.js"; import { n as createXaiVideoGenerationProviderMetadata } from "../../capability-provider-metadata-3SLkHpj3.js"; import { n as fetchXaiVideoResponse, t as downloadXaiVideo } from "../../video-generation-transport-DuRasMBV.js"; //#region extensions/xai/video-generation-provider.ts const POLL_INTERVAL_MS = 5e3; const MAX_POLL_ATTEMPTS = 120; const XAI_VIDEO_MALFORMED_RESPONSE = "xAI video generation response malformed"; const XAI_VIDEO_TERMINAL_FAILURE_STATUSES = /* @__PURE__ */ new Set([ "failed", "error", "expired", "cancelled" ]); const XAI_VIDEO_DEFAULT_DURATION_SECONDS = 8; const XAI_VIDEO_DEFAULT_ASPECT_RATIO = "16:9"; const XAI_VIDEO_DEFAULT_RESOLUTION = "480p"; async function readXaiVideoJson(response) { let payload; try { payload = await readProviderJsonResponse(response, "xAI video generation response"); } catch (error) { if (error instanceof Error && error.message.endsWith(": malformed JSON response")) throw new Error(XAI_VIDEO_MALFORMED_RESPONSE, { cause: error }); throw error; } if (!isRecord(payload)) throw new Error(XAI_VIDEO_MALFORMED_RESPONSE); return payload; } function xaiErrorMessage(payload) { const error = payload.error; if (error === void 0 || error === null) return; if (!isRecord(error)) throw new Error(XAI_VIDEO_MALFORMED_RESPONSE); return normalizeOptionalString(error.message); } function readXaiCreateResponse(payload) { return { request_id: normalizeOptionalString(payload.request_id), error: xaiErrorMessage(payload) ? { message: xaiErrorMessage(payload) } : null }; } function readXaiStatusResponse(payload) { const video = payload.video; if (video !== void 0 && video !== null && !isRecord(video)) throw new Error(XAI_VIDEO_MALFORMED_RESPONSE); return { request_id: normalizeOptionalString(payload.request_id), status: normalizeOptionalString(payload.status) ?? "", video: isRecord(video) ? { url: normalizeOptionalString(video.url) } : null, error: xaiErrorMessage(payload) ? { message: xaiErrorMessage(payload) } : null }; } function resolveXaiVideoBaseUrl(req) { return normalizeOptionalString(req.cfg?.models?.providers?.xai?.baseUrl) ?? "https://api.x.ai/v1"; } function resolveImageUrl(input) { if (!input) return; const inputUrl = normalizeOptionalString(input.url); if (inputUrl) return inputUrl; if (!input.buffer) throw new Error("xAI image-to-video input is missing image data."); return toImageDataUrl({ ...input, buffer: input.buffer, defaultMimeType: "image/png" }); } function resolveRequiredImageUrl(input) { const imageUrl = resolveImageUrl(input); if (!imageUrl) throw new Error("xAI image-to-video input is missing image data."); return imageUrl; } function isReferenceImage(input) { return normalizeOptionalString(input.role)?.toLowerCase() === "reference_image"; } function isFirstFrameImage(input) { const role = normalizeOptionalString(input.role)?.toLowerCase(); return role === void 0 || role === "first_frame"; } function validateXaiVideo15Request(req) { if (!isXaiVideo15Model(req.model)) return; if ((req.inputVideos?.length ?? 0) > 0) throw new Error("xAI grok-imagine-video-1.5 does not support video inputs."); const [inputImage, ...additionalImages] = req.inputImages ?? []; if (!inputImage || additionalImages.length > 0) throw new Error("xAI grok-imagine-video-1.5 requires exactly one first-frame image."); if (!isFirstFrameImage(inputImage)) throw new Error("xAI grok-imagine-video-1.5 supports only an ordinary or first_frame image."); } function resolveInputVideoUrl(input) { if (!input) return; const url = normalizeOptionalString(input.url); if (url) return url; if (input.buffer) throw new Error("xAI video editing currently requires a remote mp4 URL input."); throw new Error("xAI video editing input is missing video data."); } function resolveDurationSeconds(params) { if (typeof params.durationSeconds !== "number" || !Number.isFinite(params.durationSeconds)) return; const rounded = Math.round(params.durationSeconds); return Math.max(params.min ?? 1, Math.min(params.max ?? 15, rounded)); } function resolveAspectRatio(value) { const trimmed = normalizeOptionalString(value); if (!trimmed || !XAI_VIDEO_ASPECT_RATIOS.has(trimmed)) return; return trimmed; } function resolveResolution(value, options) { if (typeof value !== "string") return; const normalized = value.trim().toLowerCase(); if (normalized === "480p") return "480p"; if (normalized === "720p") return "720p"; if (normalized === "1080p") return options?.allow1080p ? "1080p" : "720p"; } function resolveXaiVideoMode(req) { const hasVideoInput = (req.inputVideos?.length ?? 0) > 0; if (!hasVideoInput && (req.inputImages ?? []).some(isReferenceImage)) return "referenceToVideo"; if (!hasVideoInput) return "generate"; return typeof resolveDurationSeconds({ durationSeconds: req.durationSeconds, min: 2, max: 10 }) === "number" ? "extend" : "edit"; } function buildCreateBody(req) { validateXaiVideo15Request(req); const inputImages = req.inputImages ?? []; const hasReferenceImages = inputImages.some(isReferenceImage); if (hasReferenceImages && !inputImages.every(isReferenceImage)) throw new Error("xAI reference-image video generation requires every image role to be reference_image."); if (!hasReferenceImages && inputImages.length > 1) throw new Error("xAI image-to-video generation supports at most one first-frame image."); if (hasReferenceImages && inputImages.length > 7) throw new Error("xAI reference-image video generation supports at most 7 reference images."); if ((req.inputVideos?.length ?? 0) > 1) throw new Error("xAI video generation supports at most one input video."); if ((req.inputImages?.length ?? 0) > 0 && (req.inputVideos?.length ?? 0) > 0) throw new Error("xAI video generation does not support image and video inputs together."); const mode = resolveXaiVideoMode(req); const body = { model: normalizeOptionalString(req.model) ?? "grok-imagine-video", prompt: req.prompt }; if (mode === "generate") { const isVideo15 = isXaiVideo15Model(req.model); const imageUrl = resolveImageUrl(req.inputImages?.[0]); if (imageUrl) body.image = { url: imageUrl }; body.duration = resolveDurationSeconds({ durationSeconds: req.durationSeconds, min: 1, max: 15 }) ?? XAI_VIDEO_DEFAULT_DURATION_SECONDS; const aspectRatio = resolveAspectRatio(req.aspectRatio); if (aspectRatio || !imageUrl) body.aspect_ratio = aspectRatio ?? XAI_VIDEO_DEFAULT_ASPECT_RATIO; body.resolution = resolveResolution(req.resolution, { allow1080p: isVideo15 }) ?? XAI_VIDEO_DEFAULT_RESOLUTION; return body; } if (mode === "referenceToVideo") { body.reference_images = inputImages.map((image) => ({ url: resolveRequiredImageUrl(image) })); body.duration = resolveDurationSeconds({ durationSeconds: req.durationSeconds, min: 1, max: 10 }) ?? XAI_VIDEO_DEFAULT_DURATION_SECONDS; body.aspect_ratio = resolveAspectRatio(req.aspectRatio) ?? XAI_VIDEO_DEFAULT_ASPECT_RATIO; body.resolution = resolveResolution(req.resolution) ?? XAI_VIDEO_DEFAULT_RESOLUTION; return body; } body.video = { url: resolveInputVideoUrl(req.inputVideos?.[0]) }; if (mode === "extend") { const duration = resolveDurationSeconds({ durationSeconds: req.durationSeconds, min: 2, max: 10 }); if (typeof duration === "number") body.duration = duration; } return body; } function resolveCreateEndpoint(req) { switch (resolveXaiVideoMode(req)) { case "edit": return "/videos/edits"; case "extend": return "/videos/extensions"; default: return "/videos/generations"; } } async function pollXaiVideo(params) { const deadline = createProviderOperationDeadline({ timeoutMs: params.timeoutMs, label: `xAI video generation request ${params.requestId}` }); for (let attempt = 0; attempt < MAX_POLL_ATTEMPTS; attempt += 1) { const { response, release } = await fetchXaiVideoResponse({ url: `${params.baseUrl}/videos/${params.requestId}`, stage: "poll", requestFailedMessage: "xAI video status request failed", auditContext: "xai-video-status", init: { method: "GET", headers: params.headers }, timeoutMs: createProviderOperationTimeoutResolver({ deadline, defaultTimeoutMs: XAI_VIDEO_DEFAULT_TIMEOUT_MS }), defaultTimeoutMs: XAI_VIDEO_DEFAULT_TIMEOUT_MS, allowPrivateNetwork: params.allowPrivateNetwork, dispatcherPolicy: params.dispatcherPolicy, fetchFn: params.fetchFn }); const payload = await (async () => { try { return readXaiStatusResponse(await readXaiVideoJson(response)); } finally { await release(); } })(); const normalizedStatus = payload.status.toLowerCase(); if (normalizedStatus === "done") return payload; if (XAI_VIDEO_TERMINAL_FAILURE_STATUSES.has(normalizedStatus)) throw new Error(normalizeOptionalString(payload.error?.message) ?? `xAI video generation ${normalizedStatus}`); await waitProviderOperationPollInterval({ deadline, pollIntervalMs: POLL_INTERVAL_MS }); } throw new Error(`xAI video generation task ${params.requestId} did not finish in time`); } function buildXaiVideoGenerationProvider() { return { ...createXaiVideoGenerationProviderMetadata(), async generateVideo(req) { const createBody = buildCreateBody(req); const createEndpoint = resolveCreateEndpoint(req); const auth = await resolveApiKeyForProvider({ provider: "xai", cfg: req.cfg, agentDir: req.agentDir, store: req.authStore }); if (!auth.apiKey) throw new Error("xAI API key missing"); const fetchFn = fetch; const deadline = createProviderOperationDeadline({ timeoutMs: req.timeoutMs, label: "xAI video generation" }); const { baseUrl, allowPrivateNetwork, headers, dispatcherPolicy } = resolveProviderHttpRequestConfig({ baseUrl: resolveXaiVideoBaseUrl(req), defaultBaseUrl: DEFAULT_XAI_VIDEO_BASE_URL, defaultHeaders: { Authorization: `Bearer ${auth.apiKey}`, "Content-Type": "application/json" }, request: sanitizeConfiguredModelProviderRequest(req.cfg?.models?.providers?.xai?.request), provider: "xai", capability: "video", transport: "http" }); const submitHeaders = new Headers(headers); submitHeaders.set("x-idempotency-key", crypto.randomUUID()); const { response, release } = await postJsonRequest({ url: `${baseUrl}${createEndpoint}`, headers: submitHeaders, body: createBody, timeoutMs: resolveProviderOperationTimeoutMs({ deadline, defaultTimeoutMs: XAI_VIDEO_DEFAULT_TIMEOUT_MS }), fetchFn, allowPrivateNetwork, dispatcherPolicy }); try { await assertOkOrThrowHttpError(response, "xAI video generation failed"); const submitted = readXaiCreateResponse(await readXaiVideoJson(response)); const requestId = normalizeOptionalString(submitted.request_id); if (!requestId) throw new Error(normalizeOptionalString(submitted.error?.message) ?? "xAI video generation response missing request_id"); const completed = await pollXaiVideo({ requestId, headers, timeoutMs: resolveProviderOperationTimeoutMs({ deadline, defaultTimeoutMs: XAI_VIDEO_DEFAULT_TIMEOUT_MS }), baseUrl, allowPrivateNetwork, dispatcherPolicy, fetchFn }); const videoUrl = normalizeOptionalString(completed.video?.url); if (!videoUrl) throw new Error(XAI_VIDEO_MALFORMED_RESPONSE); return { videos: [await downloadXaiVideo({ url: videoUrl, timeoutMs: createProviderOperationTimeoutResolver({ deadline, defaultTimeoutMs: XAI_VIDEO_DEFAULT_TIMEOUT_MS }), defaultTimeoutMs: XAI_VIDEO_DEFAULT_TIMEOUT_MS, allowPrivateNetwork, dispatcherPolicy, fetchFn, maxBytes: resolveGeneratedMediaMaxBytes(req.cfg, "video") })], model: normalizeOptionalString(req.model) ?? "grok-imagine-video", metadata: { requestId, status: completed.status, videoUrl, mode: resolveXaiVideoMode(req) } }; } finally { await release(); } } }; } //#endregion export { buildXaiVideoGenerationProvider };