mcp-duojie-image 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.js +1 -1
- package/dist/provider.js +30 -24
- package/package.json +1 -1
package/dist/config.js
CHANGED
|
@@ -12,7 +12,7 @@ export function loadConfig(overrides) {
|
|
|
12
12
|
return {
|
|
13
13
|
apiKey,
|
|
14
14
|
baseUrl: overrides?.baseUrl ?? process.env.DUOJIE_BASE_URL ?? "https://api.duojie.games",
|
|
15
|
-
model: overrides?.model ?? process.env.DUOJIE_IMAGE_MODEL ?? "gemini-3-
|
|
15
|
+
model: overrides?.model ?? process.env.DUOJIE_IMAGE_MODEL ?? "gemini-3.1-flash-image",
|
|
16
16
|
timeoutMs: 300_000,
|
|
17
17
|
};
|
|
18
18
|
}
|
package/dist/provider.js
CHANGED
|
@@ -24,35 +24,46 @@ async function fetchWithRetry(url, init, timeoutMs, retries = 0) {
|
|
|
24
24
|
}
|
|
25
25
|
throw new AppError("E_PROVIDER_BAD_REQUEST", `Network error: ${lastErr}`);
|
|
26
26
|
}
|
|
27
|
-
async function
|
|
27
|
+
async function buildParts(opts) {
|
|
28
28
|
const { prompt, referenceImages, negativePrompt } = opts;
|
|
29
29
|
const fullPrompt = negativePrompt ? `${prompt}\n\nAvoid: ${negativePrompt}` : prompt;
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
const parts = [];
|
|
31
|
+
if (referenceImages && referenceImages.length > 0) {
|
|
32
|
+
const imageParts = await Promise.all(referenceImages.map(async (imgPath) => {
|
|
33
|
+
const data = await resizeIfNeeded(imgPath);
|
|
34
|
+
return { inlineData: { mimeType: "image/png", data: data.toString("base64") } };
|
|
35
|
+
}));
|
|
36
|
+
parts.push(...imageParts);
|
|
32
37
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return
|
|
38
|
+
parts.push({ text: fullPrompt });
|
|
39
|
+
return parts;
|
|
40
|
+
}
|
|
41
|
+
function mapSize(size) {
|
|
42
|
+
if (size === "512px")
|
|
43
|
+
return "512";
|
|
44
|
+
return size; // "1K", "2K", "4K" are used as-is
|
|
40
45
|
}
|
|
41
46
|
export async function generateImage(config, opts) {
|
|
42
|
-
const
|
|
47
|
+
const parts = await buildParts(opts);
|
|
43
48
|
const model = opts.model ?? config.model;
|
|
44
49
|
const imageConfig = {
|
|
45
|
-
|
|
50
|
+
imageSize: mapSize(opts.size ?? "1K"),
|
|
46
51
|
};
|
|
47
52
|
if (opts.aspectRatio) {
|
|
48
|
-
imageConfig.
|
|
53
|
+
imageConfig.aspectRatio = opts.aspectRatio;
|
|
49
54
|
}
|
|
50
|
-
const body = JSON.stringify({
|
|
51
|
-
|
|
55
|
+
const body = JSON.stringify({
|
|
56
|
+
contents: [{ role: "user", parts }],
|
|
57
|
+
generationConfig: {
|
|
58
|
+
responseModalities: ["IMAGE"],
|
|
59
|
+
imageConfig,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
const url = `${config.baseUrl}/v1beta/models/${model}:generateContent`;
|
|
52
63
|
const res = await fetchWithRetry(url, {
|
|
53
64
|
method: "POST",
|
|
54
65
|
headers: {
|
|
55
|
-
"
|
|
66
|
+
"x-goog-api-key": config.apiKey,
|
|
56
67
|
"Content-Type": "application/json",
|
|
57
68
|
},
|
|
58
69
|
body,
|
|
@@ -68,14 +79,9 @@ export async function generateImage(config, opts) {
|
|
|
68
79
|
throw new AppError("E_PROVIDER_BAD_REQUEST", `Upstream error ${res.status}: ${text.slice(0, 200)}`);
|
|
69
80
|
}
|
|
70
81
|
const data = (await res.json());
|
|
71
|
-
const
|
|
72
|
-
if (!
|
|
82
|
+
const imagePart = data.candidates?.[0]?.content?.parts?.find((p) => p.inlineData);
|
|
83
|
+
if (!imagePart?.inlineData?.data) {
|
|
73
84
|
throw new AppError("E_PROVIDER_BAD_REQUEST", "No image in response");
|
|
74
85
|
}
|
|
75
|
-
|
|
76
|
-
const comma = imageUrl.indexOf(",");
|
|
77
|
-
if (comma === -1) {
|
|
78
|
-
throw new AppError("E_PROVIDER_BAD_REQUEST", "Unexpected image_url format");
|
|
79
|
-
}
|
|
80
|
-
return Buffer.from(imageUrl.slice(comma + 1), "base64");
|
|
86
|
+
return Buffer.from(imagePart.inlineData.data, "base64");
|
|
81
87
|
}
|