Files
openmaic/OpenMAIC/lib/media/adapters/qwen-image-adapter.ts
2026-08-16 14:58:47 +08:00

127 lines
3.7 KiB
TypeScript

/**
* Qwen Image (Alibaba Cloud / DashScope) Image Generation Adapter
*
* Uses DashScope multimodal generation API (synchronous, no polling needed).
* Endpoint: https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation
*
* Supported models:
* - qwen-image-max (highest quality)
* - z-image-turbo (fast, good quality)
*
* API docs: https://help.aliyun.com/zh/model-studio/developer-reference/text-to-image
*/
import type {
ImageGenerationConfig,
ImageGenerationOptions,
ImageGenerationResult,
} from '../types';
import { probeAuth } from '../probe-auth';
const DEFAULT_MODEL = 'qwen-image-max';
const DEFAULT_BASE_URL = 'https://dashscope.aliyuncs.com';
/**
* Map our width x height to DashScope size format "WxH".
* Common sizes: 1024*1024, 1280*720, 1664*928, 1120*1440, etc.
*/
function resolveDashScopeSize(options: ImageGenerationOptions): string {
const w = options.width || 1024;
const h = options.height || 576;
return `${w}*${h}`;
}
/**
* Lightweight connectivity test — validates API key by making a minimal
* request. 401/403 means key invalid; other errors mean key is valid.
*/
export async function testQwenImageConnectivity(
config: ImageGenerationConfig,
): Promise<{ success: boolean; message: string }> {
const baseUrl = config.baseUrl || DEFAULT_BASE_URL;
return probeAuth({
providerName: 'Qwen Image',
request: () =>
fetch(`${baseUrl}/api/v1/services/aigc/multimodal-generation/generation`, {
method: 'POST',
redirect: 'manual',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${config.apiKey}`,
},
body: JSON.stringify({
model: config.model || DEFAULT_MODEL,
input: { messages: [{ role: 'user', content: [{ text: '' }] }] },
parameters: { size: '1*1' },
}),
}),
});
}
export async function generateWithQwenImage(
config: ImageGenerationConfig,
options: ImageGenerationOptions,
): Promise<ImageGenerationResult> {
const baseUrl = config.baseUrl || DEFAULT_BASE_URL;
const response = await fetch(`${baseUrl}/api/v1/services/aigc/multimodal-generation/generation`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${config.apiKey}`,
},
body: JSON.stringify({
model: config.model || DEFAULT_MODEL,
input: {
messages: [
{
role: 'user',
content: [
{
text: options.prompt,
},
],
},
],
},
parameters: {
negative_prompt: options.negativePrompt || undefined,
prompt_extend: true,
watermark: false,
size: resolveDashScopeSize(options),
},
}),
});
if (!response.ok) {
const text = await response.text();
throw new Error(`Qwen Image generation failed (${response.status}): ${text}`);
}
const data = await response.json();
// DashScope multimodal generation response format:
// { output: { choices: [{ message: { content: [{ image: "url" }] } }] } }
const choices = data.output?.choices;
if (!choices || choices.length === 0) {
// Check for error in response
if (data.code || data.message) {
throw new Error(`Qwen Image error: ${data.code} - ${data.message}`);
}
throw new Error('Qwen Image returned empty response');
}
const content = choices[0]?.message?.content;
const imageContent = content?.find((c: { image?: string }) => c.image);
if (!imageContent?.image) {
throw new Error('Qwen Image response missing image URL');
}
return {
url: imageContent.image,
width: options.width || 1024,
height: options.height || 576,
};
}