From 8fdaa5da49362ebe4565ab23d0c006b2769108bf Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 6 Apr 2026 18:52:15 +0100 Subject: [PATCH] refactor: dedupe vydra provider request helpers --- extensions/vydra/image-generation-provider.ts | 70 ++++------------ extensions/vydra/shared.ts | 82 ++++++++++++++++++- extensions/vydra/video-generation-provider.ts | 70 ++++------------ 3 files changed, 115 insertions(+), 107 deletions(-) diff --git a/extensions/vydra/image-generation-provider.ts b/extensions/vydra/image-generation-provider.ts index 7715ddcb036..c2c33b88e12 100644 --- a/extensions/vydra/image-generation-provider.ts +++ b/extensions/vydra/image-generation-provider.ts @@ -1,21 +1,14 @@ import type { ImageGenerationProvider } from "openclaw/plugin-sdk/image-generation"; import { isProviderApiKeyConfigured } from "openclaw/plugin-sdk/provider-auth"; -import { resolveApiKeyForProvider } from "openclaw/plugin-sdk/provider-auth-runtime"; +import { assertOkOrThrowHttpError, postJsonRequest } from "openclaw/plugin-sdk/provider-http"; import { - assertOkOrThrowHttpError, - postJsonRequest, - resolveProviderHttpRequestConfig, -} from "openclaw/plugin-sdk/provider-http"; -import { - DEFAULT_VYDRA_BASE_URL, DEFAULT_VYDRA_IMAGE_MODEL, downloadVydraAsset, extractVydraResultUrls, - resolveVydraBaseUrlFromConfig, - resolveVydraErrorMessage, + resolveCompletedVydraPayload, resolveVydraResponseJobId, resolveVydraResponseStatus, - waitForVydraJob, + resolveVydraRequestContext, } from "./shared.js"; export function buildVydraImageGenerationProvider(): ImageGenerationProvider { @@ -55,29 +48,12 @@ export function buildVydraImageGenerationProvider(): ImageGenerationProvider { throw new Error("Vydra image generation supports at most one image per request."); } - const auth = await resolveApiKeyForProvider({ - provider: "vydra", - cfg: req.cfg, - agentDir: req.agentDir, - store: req.authStore, - }); - if (!auth.apiKey) { - throw new Error("Vydra API key missing"); - } - - const fetchFn = fetch; - const { baseUrl, allowPrivateNetwork, headers, dispatcherPolicy } = - resolveProviderHttpRequestConfig({ - baseUrl: resolveVydraBaseUrlFromConfig(req.cfg), - defaultBaseUrl: DEFAULT_VYDRA_BASE_URL, - allowPrivateNetwork: false, - defaultHeaders: { - Authorization: `Bearer ${auth.apiKey}`, - "Content-Type": "application/json", - }, - provider: "vydra", + const { fetchFn, baseUrl, allowPrivateNetwork, headers, dispatcherPolicy } = + await resolveVydraRequestContext({ + cfg: req.cfg, + agentDir: req.agentDir, + authStore: req.authStore, capability: "image", - transport: "http", }); const model = req.model?.trim() || DEFAULT_VYDRA_IMAGE_MODEL; @@ -97,27 +73,15 @@ export function buildVydraImageGenerationProvider(): ImageGenerationProvider { try { await assertOkOrThrowHttpError(response, "Vydra image generation failed"); const submitted = await response.json(); - const completedPayload = - resolveVydraResponseStatus(submitted) === "completed" || - extractVydraResultUrls(submitted, "image").length > 0 - ? submitted - : await (() => { - const jobId = resolveVydraResponseJobId(submitted); - if (!jobId) { - throw new Error( - resolveVydraErrorMessage(submitted) ?? - "Vydra image generation response missing job id", - ); - } - return waitForVydraJob({ - baseUrl, - jobId, - headers, - timeoutMs: req.timeoutMs, - fetchFn, - kind: "image", - }); - })(); + const completedPayload = await resolveCompletedVydraPayload({ + submitted, + baseUrl, + headers, + timeoutMs: req.timeoutMs, + fetchFn, + kind: "image", + missingJobIdMessage: "Vydra image generation response missing job id", + }); const imageUrl = extractVydraResultUrls(completedPayload, "image")[0]; if (!imageUrl) { throw new Error("Vydra image generation completed without an image URL"); diff --git a/extensions/vydra/shared.ts b/extensions/vydra/shared.ts index 5552144904b..0ecb4dd5ae2 100644 --- a/extensions/vydra/shared.ts +++ b/extensions/vydra/shared.ts @@ -1,4 +1,11 @@ -import { assertOkOrThrowHttpError, fetchWithTimeout } from "openclaw/plugin-sdk/provider-http"; +import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; +import { resolveApiKeyForProvider } from "openclaw/plugin-sdk/provider-auth-runtime"; +import { + assertOkOrThrowHttpError, + fetchWithTimeout, + resolveProviderHttpRequestConfig, +} from "openclaw/plugin-sdk/provider-http"; +import type { AuthStoreDataV1 } from "openclaw/plugin-sdk/provider-types"; export const DEFAULT_VYDRA_BASE_URL = "https://www.vydra.ai/api/v1"; export const DEFAULT_VYDRA_IMAGE_MODEL = "grok-imagine"; @@ -74,6 +81,50 @@ export function resolveVydraBaseUrlFromConfig(cfg: unknown): string { return normalizeVydraBaseUrl(trimToUndefined(vydra?.baseUrl)); } +export async function resolveVydraRequestContext(params: { + cfg: OpenClawConfig; + agentDir?: string; + authStore?: AuthStoreDataV1; + capability: "image" | "video"; +}): Promise<{ + fetchFn: typeof fetch; + baseUrl: string; + allowPrivateNetwork: boolean; + headers: Headers; + dispatcherPolicy: ReturnType["dispatcherPolicy"]; +}> { + const auth = await resolveApiKeyForProvider({ + provider: "vydra", + cfg: params.cfg, + agentDir: params.agentDir, + store: params.authStore, + }); + if (!auth.apiKey) { + throw new Error("Vydra API key missing"); + } + const fetchFn = fetch; + const { baseUrl, allowPrivateNetwork, headers, dispatcherPolicy } = + resolveProviderHttpRequestConfig({ + baseUrl: resolveVydraBaseUrlFromConfig(params.cfg), + defaultBaseUrl: DEFAULT_VYDRA_BASE_URL, + allowPrivateNetwork: false, + defaultHeaders: { + Authorization: `Bearer ${auth.apiKey}`, + "Content-Type": "application/json", + }, + provider: "vydra", + capability: params.capability, + transport: "http", + }); + return { + fetchFn, + baseUrl, + allowPrivateNetwork, + headers, + dispatcherPolicy, + }; +} + export function resolveVydraResponseJobId(payload: unknown): string | undefined { const object = asObject(payload) as VydraJobPayload | undefined; return trimToUndefined(object?.jobId) ?? trimToUndefined(object?.id); @@ -216,3 +267,32 @@ export async function waitForVydraJob(params: { } throw new Error(`Vydra job ${params.jobId} did not finish in time`); } + +export async function resolveCompletedVydraPayload(params: { + submitted: unknown; + baseUrl: string; + headers: Headers; + timeoutMs?: number; + fetchFn: typeof fetch; + kind: VydraMediaKind; + missingJobIdMessage: string; +}): Promise { + if ( + resolveVydraResponseStatus(params.submitted) === "completed" || + extractVydraResultUrls(params.submitted, params.kind).length > 0 + ) { + return params.submitted; + } + const jobId = resolveVydraResponseJobId(params.submitted); + if (!jobId) { + throw new Error(resolveVydraErrorMessage(params.submitted) ?? params.missingJobIdMessage); + } + return waitForVydraJob({ + baseUrl: params.baseUrl, + jobId, + headers: params.headers, + timeoutMs: params.timeoutMs, + fetchFn: params.fetchFn, + kind: params.kind, + }); +} diff --git a/extensions/vydra/video-generation-provider.ts b/extensions/vydra/video-generation-provider.ts index be09b246a03..17432163ff0 100644 --- a/extensions/vydra/video-generation-provider.ts +++ b/extensions/vydra/video-generation-provider.ts @@ -1,21 +1,14 @@ import { isProviderApiKeyConfigured } from "openclaw/plugin-sdk/provider-auth"; -import { resolveApiKeyForProvider } from "openclaw/plugin-sdk/provider-auth-runtime"; -import { - assertOkOrThrowHttpError, - postJsonRequest, - resolveProviderHttpRequestConfig, -} from "openclaw/plugin-sdk/provider-http"; +import { assertOkOrThrowHttpError, postJsonRequest } from "openclaw/plugin-sdk/provider-http"; import type { VideoGenerationProvider } from "openclaw/plugin-sdk/video-generation"; import { - DEFAULT_VYDRA_BASE_URL, DEFAULT_VYDRA_VIDEO_MODEL, downloadVydraAsset, extractVydraResultUrls, - resolveVydraBaseUrlFromConfig, - resolveVydraErrorMessage, + resolveCompletedVydraPayload, resolveVydraResponseJobId, resolveVydraResponseStatus, - waitForVydraJob, + resolveVydraRequestContext, } from "./shared.js"; const VYDRA_KLING_MODEL = "kling"; @@ -80,29 +73,12 @@ export function buildVydraVideoGenerationProvider(): VideoGenerationProvider { throw new Error("Vydra video generation does not support video reference inputs."); } - const auth = await resolveApiKeyForProvider({ - provider: "vydra", - cfg: req.cfg, - agentDir: req.agentDir, - store: req.authStore, - }); - if (!auth.apiKey) { - throw new Error("Vydra API key missing"); - } - - const fetchFn = fetch; - const { baseUrl, allowPrivateNetwork, headers, dispatcherPolicy } = - resolveProviderHttpRequestConfig({ - baseUrl: resolveVydraBaseUrlFromConfig(req.cfg), - defaultBaseUrl: DEFAULT_VYDRA_BASE_URL, - allowPrivateNetwork: false, - defaultHeaders: { - Authorization: `Bearer ${auth.apiKey}`, - "Content-Type": "application/json", - }, - provider: "vydra", + const { fetchFn, baseUrl, allowPrivateNetwork, headers, dispatcherPolicy } = + await resolveVydraRequestContext({ + cfg: req.cfg, + agentDir: req.agentDir, + authStore: req.authStore, capability: "video", - transport: "http", }); const { model, body } = resolveVydraVideoRequestBody(req); const { response, release } = await postJsonRequest({ @@ -118,27 +94,15 @@ export function buildVydraVideoGenerationProvider(): VideoGenerationProvider { try { await assertOkOrThrowHttpError(response, "Vydra video generation failed"); const submitted = await response.json(); - const completedPayload = - resolveVydraResponseStatus(submitted) === "completed" || - extractVydraResultUrls(submitted, "video").length > 0 - ? submitted - : await (() => { - const jobId = resolveVydraResponseJobId(submitted); - if (!jobId) { - throw new Error( - resolveVydraErrorMessage(submitted) ?? - "Vydra video generation response missing job id", - ); - } - return waitForVydraJob({ - baseUrl, - jobId, - headers, - timeoutMs: req.timeoutMs, - fetchFn, - kind: "video", - }); - })(); + const completedPayload = await resolveCompletedVydraPayload({ + submitted, + baseUrl, + headers, + timeoutMs: req.timeoutMs, + fetchFn, + kind: "video", + missingJobIdMessage: "Vydra video generation response missing job id", + }); const videoUrl = extractVydraResultUrls(completedPayload, "video")[0]; if (!videoUrl) { throw new Error("Vydra video generation completed without a video URL");