Files
openclaw/extensions/vydra/image-generation-provider.ts
Peter Steinberger 990fdcf6fd fix(providers): recognize configured media credentials across provider boundaries (#118761)
* fix(providers): honor configured media credentials across owner boundaries

* test(providers): align configured auth fixtures and derived SDK surfaces

* fix(providers): enforce shared DashScope media credential policy
2026-08-03 13:26:31 -07:00

122 lines
3.9 KiB
TypeScript

// Vydra provider module implements model/runtime integration.
import type { ImageGenerationProvider } from "openclaw/plugin-sdk/image-generation";
import { resolveGeneratedMediaMaxBytes } from "openclaw/plugin-sdk/media-generation-runtime";
import { isProviderApiKeyConfigured } from "openclaw/plugin-sdk/provider-auth";
import {
assertOkOrThrowHttpError,
postJsonRequest,
readProviderJsonResponse,
} from "openclaw/plugin-sdk/provider-http";
import {
DEFAULT_VYDRA_IMAGE_MODEL,
downloadVydraAsset,
extractVydraResultUrls,
resolveCompletedVydraPayload,
resolveVydraResponseJobId,
resolveVydraResponseStatus,
resolveVydraRequestContext,
} from "./shared.js";
export function buildVydraImageGenerationProvider(): ImageGenerationProvider {
return {
id: "vydra",
label: "Vydra",
defaultModel: DEFAULT_VYDRA_IMAGE_MODEL,
models: [DEFAULT_VYDRA_IMAGE_MODEL],
isConfigured: (ctx) => isProviderApiKeyConfigured({ provider: "vydra", ...ctx }),
capabilities: {
generate: {
maxCount: 1,
supportsSize: false,
supportsAspectRatio: false,
supportsResolution: false,
},
edit: {
enabled: false,
maxCount: 1,
maxInputImages: 0,
supportsSize: false,
supportsAspectRatio: false,
supportsResolution: false,
},
},
async generateImage(req) {
if ((req.inputImages?.length ?? 0) > 0) {
throw new Error(
"Vydra image generation currently supports text-to-image only in the Vydra plugin.",
);
}
if ((req.count ?? 1) > 1) {
throw new Error("Vydra image generation supports at most one image per request.");
}
const { fetchFn, baseUrl, requestPolicy } = await resolveVydraRequestContext({
cfg: req.cfg,
agentDir: req.agentDir,
authStore: req.authStore,
capability: "image",
ssrfPolicy: req.ssrfPolicy,
});
const model = req.model?.trim() || DEFAULT_VYDRA_IMAGE_MODEL;
const { response, release } = await postJsonRequest({
url: `${baseUrl}/models/${model}`,
headers: requestPolicy.headers,
body: {
prompt: req.prompt,
model: "text-to-image",
},
timeoutMs: req.timeoutMs,
fetchFn,
allowPrivateNetwork: requestPolicy.allowPrivateNetwork,
ssrfPolicy: requestPolicy.ssrfPolicy,
dispatcherPolicy: requestPolicy.dispatcherPolicy,
});
try {
await assertOkOrThrowHttpError(response, "Vydra image generation failed");
const submitted = await readProviderJsonResponse(response, "vydra.image-generation");
const completedPayload = await resolveCompletedVydraPayload({
submitted,
baseUrl,
timeoutMs: req.timeoutMs,
fetchFn,
kind: "image",
missingJobIdMessage: "Vydra image generation response missing job id",
requestPolicy,
});
const imageUrl = extractVydraResultUrls(completedPayload, "image")[0];
if (!imageUrl) {
throw new Error("Vydra image generation completed without an image URL");
}
const image = await downloadVydraAsset({
url: imageUrl,
kind: "image",
timeoutMs: req.timeoutMs,
fetchFn,
maxBytes: resolveGeneratedMediaMaxBytes(req.cfg, "image"),
requestPolicy,
});
return {
images: [
{
buffer: image.buffer,
mimeType: image.mimeType,
fileName: image.fileName,
},
],
model,
metadata: {
jobId:
resolveVydraResponseJobId(completedPayload) ?? resolveVydraResponseJobId(submitted),
imageUrl,
status: resolveVydraResponseStatus(completedPayload) ?? "completed",
},
};
} finally {
await release();
}
},
};
}