mcp-duojie-image 0.5.3 → 0.5.5

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.
Files changed (2) hide show
  1. package/dist/provider.js +50 -7
  2. package/package.json +1 -1
package/dist/provider.js CHANGED
@@ -46,18 +46,57 @@ function mapSize(size) {
46
46
  function isOpenAIImageModel(model) {
47
47
  return model === "gpt-image-2";
48
48
  }
49
+ function normalizeAspectRatio(aspectRatio) {
50
+ const normalized = aspectRatio?.trim().replace(":", ":").replace(/\s+/g, "");
51
+ const match = normalized?.match(/^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?)$/);
52
+ if (!match)
53
+ return undefined;
54
+ const width = Number(match[1]);
55
+ const height = Number(match[2]);
56
+ if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
57
+ return undefined;
58
+ }
59
+ return `${match[1]}:${match[2]}`;
60
+ }
61
+ function parseAspectRatio(aspectRatio) {
62
+ const normalized = normalizeAspectRatio(aspectRatio);
63
+ const match = normalized?.match(/^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?)$/);
64
+ if (!match)
65
+ return [1, 1];
66
+ return [Number(match[1]), Number(match[2])];
67
+ }
68
+ function mapOpenAI4KSize(aspectRatio) {
69
+ const [ratioWidth, ratioHeight] = parseAspectRatio(aspectRatio);
70
+ const maxPixels = 3840 * 2160;
71
+ const maxEdge = 3840;
72
+ let width = Math.floor(Math.sqrt((maxPixels * ratioWidth) / ratioHeight));
73
+ let height = Math.floor((width * ratioHeight) / ratioWidth);
74
+ if (width > maxEdge) {
75
+ width = maxEdge;
76
+ height = Math.floor((width * ratioHeight) / ratioWidth);
77
+ }
78
+ if (height > maxEdge) {
79
+ height = maxEdge;
80
+ width = Math.floor((height * ratioWidth) / ratioHeight);
81
+ }
82
+ width = Math.floor(width / 8) * 8;
83
+ height = Math.floor(height / 8) * 8;
84
+ return `${width}x${height}`;
85
+ }
49
86
  function mapOpenAISize(size, aspectRatio) {
50
87
  const squareBySize = {
51
88
  "512px": "512x512",
52
89
  "1K": "1024x1024",
53
90
  "2K": "2048x2048",
54
- "4K": "4096x4096",
55
91
  };
56
- if (aspectRatio === "16:9" || aspectRatio === "3:2" || aspectRatio === "4:3") {
57
- return size === "2K" ? "2048x1536" : size === "4K" ? "4096x3072" : "1536x1024";
92
+ if (size === "4K")
93
+ return mapOpenAI4KSize(aspectRatio);
94
+ const [ratioWidth, ratioHeight] = parseAspectRatio(aspectRatio);
95
+ if (ratioWidth > ratioHeight) {
96
+ return size === "2K" ? "2048x1536" : "1536x1024";
58
97
  }
59
- if (aspectRatio === "9:16" || aspectRatio === "2:3" || aspectRatio === "3:4") {
60
- return size === "2K" ? "1536x2048" : size === "4K" ? "3072x4096" : "1024x1536";
98
+ if (ratioHeight > ratioWidth) {
99
+ return size === "2K" ? "1536x2048" : "1024x1536";
61
100
  }
62
101
  return squareBySize[size] ?? "1024x1024";
63
102
  }
@@ -124,7 +163,8 @@ async function generateGeminiImage(config, opts) {
124
163
  async function generateOpenAIImage(config, opts) {
125
164
  const model = opts.model ?? config.model;
126
165
  const prompt = opts.negativePrompt ? `${opts.prompt}\n\nAvoid: ${opts.negativePrompt}` : opts.prompt;
127
- const size = mapOpenAISize(opts.size ?? "1K", opts.aspectRatio);
166
+ const aspectRatio = normalizeAspectRatio(opts.aspectRatio);
167
+ const size = mapOpenAISize(opts.size ?? "1K", aspectRatio);
128
168
  const hasReferenceImages = Boolean(opts.referenceImages?.length);
129
169
  const url = `${config.baseUrl}${hasReferenceImages ? "/v1/images/edits" : "/v1/images/generations"}`;
130
170
  let body;
@@ -136,6 +176,9 @@ async function generateOpenAIImage(config, opts) {
136
176
  form.set("model", model);
137
177
  form.set("prompt", prompt);
138
178
  form.set("size", size);
179
+ if (aspectRatio) {
180
+ form.set("aspect_ratio", aspectRatio);
181
+ }
139
182
  for (const [index, imgPath] of opts.referenceImages.entries()) {
140
183
  const data = await resizeIfNeeded(imgPath);
141
184
  const bytes = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
@@ -145,7 +188,7 @@ async function generateOpenAIImage(config, opts) {
145
188
  }
146
189
  else {
147
190
  headers = { ...headers, "Content-Type": "application/json" };
148
- body = JSON.stringify({ model, prompt, size });
191
+ body = JSON.stringify({ model, prompt, size, ...(aspectRatio ? { aspect_ratio: aspectRatio } : {}) });
149
192
  }
150
193
  const res = await fetchWithRetry(url, {
151
194
  method: "POST",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-duojie-image",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
4
4
  "description": "MCP server for image generation via duojie.games",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",