refactor: dedupe vydra provider request helpers

This commit is contained in:
Peter Steinberger
2026-04-06 18:52:15 +01:00
parent 6dfdc92bd4
commit 8fdaa5da49
3 changed files with 115 additions and 107 deletions

View File

@@ -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");

View File

@@ -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<typeof resolveProviderHttpRequestConfig>["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<unknown> {
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,
});
}

View File

@@ -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");