jimeng-cli 0.2.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/LICENSE +674 -0
- package/README.md +91 -0
- package/dist/chunk-JZY62VNI.js +4762 -0
- package/dist/chunk-JZY62VNI.js.map +1 -0
- package/dist/cli/index.cjs +6415 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.d.cts +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +1664 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.cjs +24 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.cjs +5584 -0
- package/dist/mcp/index.cjs.map +1 -0
- package/dist/mcp/index.d.cts +2 -0
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.js +831 -0
- package/dist/mcp/index.js.map +1 -0
- package/package.json +87 -0
|
@@ -0,0 +1,831 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_IMAGE_MODEL,
|
|
3
|
+
DEFAULT_MODEL,
|
|
4
|
+
buildRegionInfo,
|
|
5
|
+
environment_default,
|
|
6
|
+
generateImageComposition,
|
|
7
|
+
generateImages,
|
|
8
|
+
generateVideo,
|
|
9
|
+
getLiveModels,
|
|
10
|
+
getTaskResponse,
|
|
11
|
+
session_pool_default,
|
|
12
|
+
util_default,
|
|
13
|
+
waitForTaskResponse
|
|
14
|
+
} from "../chunk-JZY62VNI.js";
|
|
15
|
+
|
|
16
|
+
// src/mcp/index.ts
|
|
17
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
18
|
+
|
|
19
|
+
// src/mcp/config.ts
|
|
20
|
+
function parseBoolean(value, defaultValue) {
|
|
21
|
+
if (value == null) return defaultValue;
|
|
22
|
+
const normalized = value.trim().toLowerCase();
|
|
23
|
+
if (["1", "true", "yes", "on"].includes(normalized)) return true;
|
|
24
|
+
if (["0", "false", "no", "off"].includes(normalized)) return false;
|
|
25
|
+
return defaultValue;
|
|
26
|
+
}
|
|
27
|
+
function parseNumber(value, defaultValue) {
|
|
28
|
+
if (value == null) return defaultValue;
|
|
29
|
+
const parsed = Number(value);
|
|
30
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : defaultValue;
|
|
31
|
+
}
|
|
32
|
+
function loadMcpConfig() {
|
|
33
|
+
var _a;
|
|
34
|
+
const apiToken = (_a = process.env.JIMENG_API_TOKEN) == null ? void 0 : _a.trim();
|
|
35
|
+
return {
|
|
36
|
+
apiToken: apiToken || void 0,
|
|
37
|
+
httpTimeoutMs: parseNumber(process.env.MCP_HTTP_TIMEOUT_MS, 12e4),
|
|
38
|
+
enableAdvancedTools: parseBoolean(process.env.MCP_ENABLE_ADVANCED_TOOLS, true),
|
|
39
|
+
requireRunConfirm: parseBoolean(process.env.MCP_REQUIRE_RUN_CONFIRM, true)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/mcp/server.ts
|
|
44
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
45
|
+
|
|
46
|
+
// src/mcp/client.ts
|
|
47
|
+
import fs from "fs";
|
|
48
|
+
import path from "path";
|
|
49
|
+
function resolveTaskType(value) {
|
|
50
|
+
return value === "video" ? "video" : "image";
|
|
51
|
+
}
|
|
52
|
+
var JimengApiClient = class {
|
|
53
|
+
defaultToken;
|
|
54
|
+
tokenPoolReady = false;
|
|
55
|
+
constructor(config) {
|
|
56
|
+
this.defaultToken = config.apiToken;
|
|
57
|
+
}
|
|
58
|
+
resolveToken(options) {
|
|
59
|
+
return (options == null ? void 0 : options.token) || this.defaultToken;
|
|
60
|
+
}
|
|
61
|
+
async ensureTokenPoolReady() {
|
|
62
|
+
if (this.tokenPoolReady) return;
|
|
63
|
+
await session_pool_default.init();
|
|
64
|
+
this.tokenPoolReady = true;
|
|
65
|
+
}
|
|
66
|
+
async pickModelToken(requestedModel, taskType, options, requiredCapabilityTags = []) {
|
|
67
|
+
await this.ensureTokenPoolReady();
|
|
68
|
+
const token = this.resolveToken(options);
|
|
69
|
+
const tokenPick = session_pool_default.pickTokenForRequest({
|
|
70
|
+
authorization: token ? `Bearer ${token}` : void 0,
|
|
71
|
+
requestedModel,
|
|
72
|
+
taskType,
|
|
73
|
+
requiredCapabilityTags
|
|
74
|
+
});
|
|
75
|
+
if (!tokenPick.token || !tokenPick.region) {
|
|
76
|
+
throw new Error(tokenPick.reason || "Missing available token for model request");
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
token: tokenPick.token,
|
|
80
|
+
regionInfo: buildRegionInfo(tokenPick.region)
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
async pickTaskToken(options, type = "image") {
|
|
84
|
+
await this.ensureTokenPoolReady();
|
|
85
|
+
const token = this.resolveToken(options);
|
|
86
|
+
if (token) {
|
|
87
|
+
const entry = session_pool_default.getTokenEntry(token);
|
|
88
|
+
if (!(entry == null ? void 0 : entry.region)) {
|
|
89
|
+
throw new Error("Missing region for token. Register token with region in token-pool.");
|
|
90
|
+
}
|
|
91
|
+
return { token, regionInfo: buildRegionInfo(entry.region) };
|
|
92
|
+
}
|
|
93
|
+
const candidates = session_pool_default.getEntries(false).filter((item) => item.enabled && item.live !== false && item.region).filter((item) => {
|
|
94
|
+
var _a;
|
|
95
|
+
const modelHint = type === "video" ? "video" : "jimeng";
|
|
96
|
+
return !((_a = item.allowedModels) == null ? void 0 : _a.length) || item.allowedModels.some((m) => m.includes(modelHint));
|
|
97
|
+
});
|
|
98
|
+
if (candidates.length === 0) {
|
|
99
|
+
throw new Error("No token available for task request. Configure token-pool or pass token.");
|
|
100
|
+
}
|
|
101
|
+
return { token: candidates[0].token, regionInfo: buildRegionInfo(candidates[0].region) };
|
|
102
|
+
}
|
|
103
|
+
async healthCheck() {
|
|
104
|
+
return "pong";
|
|
105
|
+
}
|
|
106
|
+
async listModels(options) {
|
|
107
|
+
var _a;
|
|
108
|
+
await this.ensureTokenPoolReady();
|
|
109
|
+
const token = this.resolveToken(options);
|
|
110
|
+
const authorization = token ? `Bearer ${token}` : void 0;
|
|
111
|
+
const region = token ? (_a = session_pool_default.getTokenEntry(token)) == null ? void 0 : _a.region : void 0;
|
|
112
|
+
const result = await getLiveModels(authorization, region);
|
|
113
|
+
return {
|
|
114
|
+
source: result.source,
|
|
115
|
+
data: result.data
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
async generateImage(body, options) {
|
|
119
|
+
const model = typeof body.model === "string" && body.model.trim().length > 0 ? body.model : DEFAULT_IMAGE_MODEL;
|
|
120
|
+
const prompt = String(body.prompt || "");
|
|
121
|
+
const tokenCtx = await this.pickModelToken(model, "image", options);
|
|
122
|
+
const responseFormat = body.response_format === "b64_json" ? "b64_json" : "url";
|
|
123
|
+
const imageResult = await generateImages(
|
|
124
|
+
model,
|
|
125
|
+
prompt,
|
|
126
|
+
{
|
|
127
|
+
ratio: body.ratio,
|
|
128
|
+
resolution: body.resolution,
|
|
129
|
+
sampleStrength: body.sample_strength,
|
|
130
|
+
negativePrompt: body.negative_prompt,
|
|
131
|
+
intelligentRatio: body.intelligent_ratio,
|
|
132
|
+
wait: body.wait,
|
|
133
|
+
waitTimeoutSeconds: body.wait_timeout_seconds,
|
|
134
|
+
pollIntervalMs: body.poll_interval_ms
|
|
135
|
+
},
|
|
136
|
+
tokenCtx.token,
|
|
137
|
+
tokenCtx.regionInfo
|
|
138
|
+
);
|
|
139
|
+
if (!Array.isArray(imageResult)) {
|
|
140
|
+
return imageResult;
|
|
141
|
+
}
|
|
142
|
+
const data = responseFormat === "b64_json" ? (await Promise.all(imageResult.map((url) => util_default.fetchFileBASE64(url)))).map((b64) => ({ b64_json: b64 })) : imageResult.map((url) => ({ url }));
|
|
143
|
+
return {
|
|
144
|
+
created: util_default.unixTimestamp(),
|
|
145
|
+
data
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
async editImage(body, options) {
|
|
149
|
+
const model = typeof body.model === "string" && body.model.trim().length > 0 ? body.model : DEFAULT_IMAGE_MODEL;
|
|
150
|
+
const prompt = String(body.prompt || "");
|
|
151
|
+
const images = Array.isArray(body.images) ? body.images.filter((item) => typeof item === "string") : [];
|
|
152
|
+
const tokenCtx = await this.pickModelToken(model, "image", options);
|
|
153
|
+
const responseFormat = body.response_format === "b64_json" ? "b64_json" : "url";
|
|
154
|
+
const compositionResult = await generateImageComposition(
|
|
155
|
+
model,
|
|
156
|
+
prompt,
|
|
157
|
+
images,
|
|
158
|
+
{
|
|
159
|
+
ratio: body.ratio,
|
|
160
|
+
resolution: body.resolution,
|
|
161
|
+
sampleStrength: body.sample_strength,
|
|
162
|
+
negativePrompt: body.negative_prompt,
|
|
163
|
+
intelligentRatio: body.intelligent_ratio,
|
|
164
|
+
wait: body.wait,
|
|
165
|
+
waitTimeoutSeconds: body.wait_timeout_seconds,
|
|
166
|
+
pollIntervalMs: body.poll_interval_ms
|
|
167
|
+
},
|
|
168
|
+
tokenCtx.token,
|
|
169
|
+
tokenCtx.regionInfo
|
|
170
|
+
);
|
|
171
|
+
if (!Array.isArray(compositionResult)) {
|
|
172
|
+
return compositionResult;
|
|
173
|
+
}
|
|
174
|
+
const data = responseFormat === "b64_json" ? (await Promise.all(compositionResult.map((url) => util_default.fetchFileBASE64(url)))).map((b64) => ({ b64_json: b64 })) : compositionResult.map((url) => ({ url }));
|
|
175
|
+
return {
|
|
176
|
+
created: util_default.unixTimestamp(),
|
|
177
|
+
data,
|
|
178
|
+
input_images: images.length,
|
|
179
|
+
composition_type: "multi_image_synthesis"
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
async generateVideo(body, options) {
|
|
183
|
+
const model = typeof body.model === "string" && body.model.trim().length > 0 ? body.model : DEFAULT_MODEL;
|
|
184
|
+
const prompt = String(body.prompt || "");
|
|
185
|
+
const functionMode = typeof body.functionMode === "string" ? body.functionMode : "first_last_frames";
|
|
186
|
+
const requiredTags = functionMode === "omni_reference" ? ["omni_reference"] : [];
|
|
187
|
+
const tokenCtx = await this.pickModelToken(model, "video", options, requiredTags);
|
|
188
|
+
const videoResult = await generateVideo(
|
|
189
|
+
model,
|
|
190
|
+
prompt,
|
|
191
|
+
{
|
|
192
|
+
ratio: body.ratio,
|
|
193
|
+
resolution: body.resolution,
|
|
194
|
+
duration: body.duration,
|
|
195
|
+
filePaths: body.filePaths || body.file_paths,
|
|
196
|
+
files: body.files,
|
|
197
|
+
httpRequest: { body },
|
|
198
|
+
functionMode,
|
|
199
|
+
wait: body.wait,
|
|
200
|
+
waitTimeoutSeconds: body.wait_timeout_seconds,
|
|
201
|
+
pollIntervalMs: body.poll_interval_ms
|
|
202
|
+
},
|
|
203
|
+
tokenCtx.token,
|
|
204
|
+
tokenCtx.regionInfo
|
|
205
|
+
);
|
|
206
|
+
if (typeof videoResult !== "string") {
|
|
207
|
+
return videoResult;
|
|
208
|
+
}
|
|
209
|
+
if (body.response_format === "b64_json") {
|
|
210
|
+
const videoBase64 = await util_default.fetchFileBASE64(videoResult);
|
|
211
|
+
return {
|
|
212
|
+
created: util_default.unixTimestamp(),
|
|
213
|
+
data: [{ b64_json: videoBase64, revised_prompt: prompt }]
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
created: util_default.unixTimestamp(),
|
|
218
|
+
data: [{ url: videoResult, revised_prompt: prompt }]
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
async getTask(taskId, options, query) {
|
|
222
|
+
const type = (query == null ? void 0 : query.type) === "video" ? "video" : (query == null ? void 0 : query.type) === "image" ? "image" : void 0;
|
|
223
|
+
const tokenCtx = await this.pickTaskToken(options, resolveTaskType(type));
|
|
224
|
+
return getTaskResponse(taskId, tokenCtx.token, tokenCtx.regionInfo, {
|
|
225
|
+
type,
|
|
226
|
+
responseFormat: (query == null ? void 0 : query.response_format) === "b64_json" ? "b64_json" : "url"
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
async waitTask(taskId, body, options) {
|
|
230
|
+
const type = body.type === "video" ? "video" : body.type === "image" ? "image" : void 0;
|
|
231
|
+
const tokenCtx = await this.pickTaskToken(options, resolveTaskType(type));
|
|
232
|
+
return waitForTaskResponse(taskId, tokenCtx.token, tokenCtx.regionInfo, {
|
|
233
|
+
type,
|
|
234
|
+
responseFormat: body.response_format === "b64_json" ? "b64_json" : "url",
|
|
235
|
+
waitTimeoutSeconds: body.wait_timeout_seconds,
|
|
236
|
+
pollIntervalMs: body.poll_interval_ms
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
async generateVideoOmni(body, options, uploadFiles = []) {
|
|
240
|
+
const files = {};
|
|
241
|
+
for (const file of uploadFiles) {
|
|
242
|
+
files[file.fieldName] = {
|
|
243
|
+
filepath: file.filePath,
|
|
244
|
+
originalFilename: path.basename(file.filePath)
|
|
245
|
+
};
|
|
246
|
+
if (!fs.existsSync(file.filePath)) {
|
|
247
|
+
throw new Error(`Local file not found: ${file.filePath}`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return this.generateVideo(
|
|
251
|
+
{
|
|
252
|
+
...body,
|
|
253
|
+
functionMode: "omni_reference",
|
|
254
|
+
files
|
|
255
|
+
},
|
|
256
|
+
options
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// src/mcp/errors.ts
|
|
262
|
+
import axios from "axios";
|
|
263
|
+
var McpToolError = class extends Error {
|
|
264
|
+
code;
|
|
265
|
+
details;
|
|
266
|
+
constructor(code, message, details) {
|
|
267
|
+
super(message);
|
|
268
|
+
this.code = code;
|
|
269
|
+
this.details = details;
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
function normalizeToolError(error) {
|
|
273
|
+
var _a, _b, _c;
|
|
274
|
+
if (error instanceof McpToolError) return error;
|
|
275
|
+
if (axios.isAxiosError(error)) {
|
|
276
|
+
if (!error.response) {
|
|
277
|
+
return new McpToolError(
|
|
278
|
+
"NETWORK_ERROR",
|
|
279
|
+
"Failed to reach jimeng-cli service",
|
|
280
|
+
error.message
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
const status = error.response.status;
|
|
284
|
+
const message = String(((_b = (_a = error.response.data) == null ? void 0 : _a.error) == null ? void 0 : _b.message) || ((_c = error.response.data) == null ? void 0 : _c.message) || error.message);
|
|
285
|
+
if (status === 401 || status === 403) {
|
|
286
|
+
return new McpToolError("AUTH_ERROR", message, error.response.data);
|
|
287
|
+
}
|
|
288
|
+
if (status >= 400 && status < 500) {
|
|
289
|
+
return new McpToolError("VALIDATION_ERROR", message, error.response.data);
|
|
290
|
+
}
|
|
291
|
+
if (status >= 500) {
|
|
292
|
+
return new McpToolError("UPSTREAM_ERROR", message, error.response.data);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (error instanceof Error) {
|
|
296
|
+
return new McpToolError("INTERNAL_ERROR", error.message);
|
|
297
|
+
}
|
|
298
|
+
return new McpToolError("INTERNAL_ERROR", "Unknown internal error");
|
|
299
|
+
}
|
|
300
|
+
function formatToolError(error) {
|
|
301
|
+
const normalized = normalizeToolError(error);
|
|
302
|
+
return `[${normalized.code}] ${normalized.message}`;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// src/mcp/guards.ts
|
|
306
|
+
function assertRunConfirm(config, confirm) {
|
|
307
|
+
if (!config.requireRunConfirm) return;
|
|
308
|
+
if (confirm === "RUN") return;
|
|
309
|
+
throw new McpToolError(
|
|
310
|
+
"VALIDATION_ERROR",
|
|
311
|
+
'This tool requires explicit confirmation: set "confirm" to "RUN".'
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// src/mcp/schemas.ts
|
|
316
|
+
import * as z from "zod";
|
|
317
|
+
function buildIndexedUrlFields(prefix, max) {
|
|
318
|
+
return Object.fromEntries(
|
|
319
|
+
Array.from({ length: max }, (_, index) => [
|
|
320
|
+
`${prefix}_${index + 1}`,
|
|
321
|
+
z.string().url().optional()
|
|
322
|
+
])
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
function normalizeUniqueValues(values) {
|
|
326
|
+
return [...new Set(values.map((item) => item.trim()).filter(Boolean))];
|
|
327
|
+
}
|
|
328
|
+
var healthCheckInputSchema = z.object({});
|
|
329
|
+
var listModelsInputSchema = z.object({
|
|
330
|
+
token: z.string().optional()
|
|
331
|
+
});
|
|
332
|
+
var generateImageInputSchema = z.object({
|
|
333
|
+
prompt: z.string().min(1),
|
|
334
|
+
model: z.string().optional(),
|
|
335
|
+
negative_prompt: z.string().optional(),
|
|
336
|
+
ratio: z.string().optional(),
|
|
337
|
+
resolution: z.string().optional(),
|
|
338
|
+
intelligent_ratio: z.boolean().optional(),
|
|
339
|
+
sample_strength: z.number().optional(),
|
|
340
|
+
response_format: z.enum(["url", "b64_json"]).optional(),
|
|
341
|
+
wait: z.boolean().optional(),
|
|
342
|
+
wait_timeout_seconds: z.number().int().positive().optional(),
|
|
343
|
+
poll_interval_ms: z.number().int().positive().optional(),
|
|
344
|
+
token: z.string().optional(),
|
|
345
|
+
confirm: z.string().optional()
|
|
346
|
+
});
|
|
347
|
+
var editImageInputSchema = z.object({
|
|
348
|
+
prompt: z.string().min(1),
|
|
349
|
+
images: z.array(z.string().url()).min(1).max(10),
|
|
350
|
+
model: z.string().optional(),
|
|
351
|
+
negative_prompt: z.string().optional(),
|
|
352
|
+
ratio: z.string().optional(),
|
|
353
|
+
resolution: z.string().optional(),
|
|
354
|
+
intelligent_ratio: z.boolean().optional(),
|
|
355
|
+
sample_strength: z.number().optional(),
|
|
356
|
+
response_format: z.enum(["url", "b64_json"]).optional(),
|
|
357
|
+
wait: z.boolean().optional(),
|
|
358
|
+
wait_timeout_seconds: z.number().int().positive().optional(),
|
|
359
|
+
poll_interval_ms: z.number().int().positive().optional(),
|
|
360
|
+
token: z.string().optional(),
|
|
361
|
+
confirm: z.string().optional()
|
|
362
|
+
});
|
|
363
|
+
var generateVideoInputSchema = z.object({
|
|
364
|
+
prompt: z.string().min(1),
|
|
365
|
+
model: z.string().optional(),
|
|
366
|
+
ratio: z.string().optional(),
|
|
367
|
+
resolution: z.string().optional(),
|
|
368
|
+
duration: z.number().int().min(4).max(15).optional(),
|
|
369
|
+
wait: z.boolean().optional(),
|
|
370
|
+
wait_timeout_seconds: z.number().int().positive().optional(),
|
|
371
|
+
poll_interval_ms: z.number().int().positive().optional(),
|
|
372
|
+
token: z.string().optional(),
|
|
373
|
+
confirm: z.string().optional()
|
|
374
|
+
});
|
|
375
|
+
var generateVideoOmniInputSchema = z.object({
|
|
376
|
+
prompt: z.string().min(1),
|
|
377
|
+
model: z.string().optional(),
|
|
378
|
+
ratio: z.string().optional(),
|
|
379
|
+
resolution: z.string().optional(),
|
|
380
|
+
duration: z.number().int().min(4).max(15).optional(),
|
|
381
|
+
response_format: z.enum(["url", "b64_json"]).optional(),
|
|
382
|
+
wait: z.boolean().optional(),
|
|
383
|
+
wait_timeout_seconds: z.number().int().positive().optional(),
|
|
384
|
+
poll_interval_ms: z.number().int().positive().optional(),
|
|
385
|
+
file_paths: z.array(z.string().url()).max(9).optional(),
|
|
386
|
+
filePaths: z.array(z.string().url()).max(9).optional(),
|
|
387
|
+
image_urls: z.array(z.string().url()).max(9).optional(),
|
|
388
|
+
video_urls: z.array(z.string().url()).max(3).optional(),
|
|
389
|
+
image_files: z.array(z.string()).max(9).optional(),
|
|
390
|
+
video_files: z.array(z.string()).max(3).optional(),
|
|
391
|
+
...buildIndexedUrlFields("image_file", 9),
|
|
392
|
+
...buildIndexedUrlFields("video_file", 3),
|
|
393
|
+
token: z.string().optional(),
|
|
394
|
+
confirm: z.string().optional()
|
|
395
|
+
}).superRefine((value, ctx) => {
|
|
396
|
+
var _a, _b;
|
|
397
|
+
const imageSlotUrls = Array.from({ length: 9 }, (_, index) => value[`image_file_${index + 1}`]);
|
|
398
|
+
const videoSlotUrls = Array.from({ length: 3 }, (_, index) => value[`video_file_${index + 1}`]);
|
|
399
|
+
const imageCount = normalizeUniqueValues(value.image_urls || []).length + (((_a = value.image_files) == null ? void 0 : _a.length) || 0) + normalizeUniqueValues([...value.file_paths || [], ...value.filePaths || []]).length + normalizeUniqueValues(imageSlotUrls.filter((item) => typeof item === "string")).length;
|
|
400
|
+
const videoCount = normalizeUniqueValues(value.video_urls || []).length + (((_b = value.video_files) == null ? void 0 : _b.length) || 0) + normalizeUniqueValues(videoSlotUrls.filter((item) => typeof item === "string")).length;
|
|
401
|
+
if (imageCount > 9) {
|
|
402
|
+
ctx.addIssue({
|
|
403
|
+
code: z.ZodIssueCode.custom,
|
|
404
|
+
message: "Omni mode supports at most 9 images."
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
if (videoCount > 3) {
|
|
408
|
+
ctx.addIssue({
|
|
409
|
+
code: z.ZodIssueCode.custom,
|
|
410
|
+
message: "Omni mode supports at most 3 videos."
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
if (imageCount + videoCount > 12) {
|
|
414
|
+
ctx.addIssue({
|
|
415
|
+
code: z.ZodIssueCode.custom,
|
|
416
|
+
message: "Omni mode supports at most 12 total materials."
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
if (imageCount + videoCount === 0) {
|
|
420
|
+
ctx.addIssue({
|
|
421
|
+
code: z.ZodIssueCode.custom,
|
|
422
|
+
message: "Omni mode requires at least one material."
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
var getTaskInputSchema = z.object({
|
|
427
|
+
task_id: z.string().min(1),
|
|
428
|
+
type: z.enum(["image", "video"]).optional(),
|
|
429
|
+
response_format: z.enum(["url", "b64_json"]).optional(),
|
|
430
|
+
token: z.string().optional()
|
|
431
|
+
});
|
|
432
|
+
var waitTaskInputSchema = z.object({
|
|
433
|
+
task_id: z.string().min(1),
|
|
434
|
+
type: z.enum(["image", "video"]).optional(),
|
|
435
|
+
response_format: z.enum(["url", "b64_json"]).optional(),
|
|
436
|
+
wait_timeout_seconds: z.number().int().positive().optional(),
|
|
437
|
+
poll_interval_ms: z.number().int().positive().optional(),
|
|
438
|
+
token: z.string().optional()
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
// src/mcp/result.ts
|
|
442
|
+
function toStructuredContent(data) {
|
|
443
|
+
if (data != null && typeof data === "object" && !Array.isArray(data)) {
|
|
444
|
+
return data;
|
|
445
|
+
}
|
|
446
|
+
return { data };
|
|
447
|
+
}
|
|
448
|
+
function toToolResult(data) {
|
|
449
|
+
return {
|
|
450
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
451
|
+
structuredContent: toStructuredContent(data)
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
async function withToolError(fn) {
|
|
455
|
+
try {
|
|
456
|
+
return await fn();
|
|
457
|
+
} catch (error) {
|
|
458
|
+
throw new Error(formatToolError(error));
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// src/mcp/tool-factory.ts
|
|
463
|
+
function registerSafeTool(server, name, options, handler) {
|
|
464
|
+
const { title, description, inputSchema, annotations } = options;
|
|
465
|
+
server.registerTool(
|
|
466
|
+
name,
|
|
467
|
+
{
|
|
468
|
+
title,
|
|
469
|
+
description,
|
|
470
|
+
inputSchema,
|
|
471
|
+
...annotations ? { annotations } : {}
|
|
472
|
+
},
|
|
473
|
+
(async (args) => withToolError(async () => {
|
|
474
|
+
const parsedArgs = inputSchema.parse(args);
|
|
475
|
+
const result = await handler(parsedArgs);
|
|
476
|
+
return toToolResult(result);
|
|
477
|
+
}))
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// src/mcp/tools/edit-image.ts
|
|
482
|
+
function registerEditImageTool({ server, config, client }) {
|
|
483
|
+
registerSafeTool(
|
|
484
|
+
server,
|
|
485
|
+
"edit_image",
|
|
486
|
+
{
|
|
487
|
+
title: "Edit Image",
|
|
488
|
+
description: "Compose image from prompt and image URLs",
|
|
489
|
+
inputSchema: editImageInputSchema
|
|
490
|
+
},
|
|
491
|
+
async (args) => {
|
|
492
|
+
assertRunConfirm(config, args.confirm);
|
|
493
|
+
return client.editImage(
|
|
494
|
+
{
|
|
495
|
+
prompt: args.prompt,
|
|
496
|
+
images: args.images,
|
|
497
|
+
model: args.model,
|
|
498
|
+
negative_prompt: args.negative_prompt,
|
|
499
|
+
ratio: args.ratio,
|
|
500
|
+
resolution: args.resolution || "1k",
|
|
501
|
+
intelligent_ratio: args.intelligent_ratio,
|
|
502
|
+
sample_strength: args.sample_strength,
|
|
503
|
+
response_format: args.response_format,
|
|
504
|
+
wait: args.wait,
|
|
505
|
+
wait_timeout_seconds: args.wait_timeout_seconds,
|
|
506
|
+
poll_interval_ms: args.poll_interval_ms
|
|
507
|
+
},
|
|
508
|
+
{ token: args.token }
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// src/mcp/tools/generate-image.ts
|
|
515
|
+
function registerGenerateImageTool({ server, config, client }) {
|
|
516
|
+
registerSafeTool(
|
|
517
|
+
server,
|
|
518
|
+
"generate_image",
|
|
519
|
+
{
|
|
520
|
+
title: "Generate Image",
|
|
521
|
+
description: "Generate image from prompt",
|
|
522
|
+
inputSchema: generateImageInputSchema
|
|
523
|
+
},
|
|
524
|
+
async (args) => {
|
|
525
|
+
assertRunConfirm(config, args.confirm);
|
|
526
|
+
return client.generateImage(
|
|
527
|
+
{
|
|
528
|
+
prompt: args.prompt,
|
|
529
|
+
model: args.model,
|
|
530
|
+
negative_prompt: args.negative_prompt,
|
|
531
|
+
ratio: args.ratio,
|
|
532
|
+
resolution: args.resolution || "1k",
|
|
533
|
+
intelligent_ratio: args.intelligent_ratio,
|
|
534
|
+
sample_strength: args.sample_strength,
|
|
535
|
+
response_format: args.response_format,
|
|
536
|
+
wait: args.wait,
|
|
537
|
+
wait_timeout_seconds: args.wait_timeout_seconds,
|
|
538
|
+
poll_interval_ms: args.poll_interval_ms
|
|
539
|
+
},
|
|
540
|
+
{ token: args.token }
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// src/mcp/tools/video-utils.ts
|
|
547
|
+
import fs2 from "fs";
|
|
548
|
+
var MAX_IMAGE_SLOTS = 9;
|
|
549
|
+
var MAX_VIDEO_SLOTS = 3;
|
|
550
|
+
function buildBaseVideoPayload(args, functionMode) {
|
|
551
|
+
return {
|
|
552
|
+
prompt: args.prompt,
|
|
553
|
+
model: args.model,
|
|
554
|
+
ratio: args.ratio,
|
|
555
|
+
resolution: args.resolution,
|
|
556
|
+
duration: args.duration ?? 5,
|
|
557
|
+
response_format: args.response_format,
|
|
558
|
+
wait: args.wait,
|
|
559
|
+
wait_timeout_seconds: args.wait_timeout_seconds,
|
|
560
|
+
poll_interval_ms: args.poll_interval_ms,
|
|
561
|
+
functionMode
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
function uniqueStrings(values) {
|
|
565
|
+
return [...new Set(values.map((item) => item.trim()).filter(Boolean))];
|
|
566
|
+
}
|
|
567
|
+
function collectStringArray(values) {
|
|
568
|
+
if (!Array.isArray(values)) return [];
|
|
569
|
+
return values.filter((item) => typeof item === "string");
|
|
570
|
+
}
|
|
571
|
+
function collectIndexedSlotUrls(args, prefix, max) {
|
|
572
|
+
const values = /* @__PURE__ */ new Map();
|
|
573
|
+
for (let i = 1; i <= max; i++) {
|
|
574
|
+
const key = `${prefix}_${i}`;
|
|
575
|
+
const slot = args[key];
|
|
576
|
+
if (typeof slot === "string" && slot.length > 0) {
|
|
577
|
+
values.set(i, slot);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return values;
|
|
581
|
+
}
|
|
582
|
+
function assertLocalFilesExist(paths) {
|
|
583
|
+
for (const filePath of paths) {
|
|
584
|
+
if (!fs2.existsSync(filePath)) {
|
|
585
|
+
throw new McpToolError("VALIDATION_ERROR", `Local file not found: ${filePath}`);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
function takeNextAvailableSlot(occupiedSlots, maxSlots, materialLabel) {
|
|
590
|
+
for (let i = 1; i <= maxSlots; i++) {
|
|
591
|
+
if (!occupiedSlots.has(i)) {
|
|
592
|
+
occupiedSlots.add(i);
|
|
593
|
+
return i;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
throw new McpToolError(
|
|
597
|
+
"VALIDATION_ERROR",
|
|
598
|
+
`No available ${materialLabel} slot. Maximum supported: ${maxSlots}.`
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
function appendUrlMaterials(body, occupiedSlots, urls, prefix, maxSlots, materialLabel) {
|
|
602
|
+
for (const url of urls) {
|
|
603
|
+
const slot = takeNextAvailableSlot(occupiedSlots, maxSlots, materialLabel);
|
|
604
|
+
body[`${prefix}_${slot}`] = url;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
function appendFileMaterials(uploadFiles, occupiedSlots, filePaths, prefix, maxSlots, materialLabel) {
|
|
608
|
+
for (const filePath of filePaths) {
|
|
609
|
+
const slot = takeNextAvailableSlot(occupiedSlots, maxSlots, materialLabel);
|
|
610
|
+
uploadFiles.push({
|
|
611
|
+
fieldName: `${prefix}_${slot}`,
|
|
612
|
+
filePath
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// src/mcp/tools/generate-video-omni.ts
|
|
618
|
+
function normalizeOmniPayload(args) {
|
|
619
|
+
const imageSlotUrls = collectIndexedSlotUrls(args, "image_file", MAX_IMAGE_SLOTS);
|
|
620
|
+
const videoSlotUrls = collectIndexedSlotUrls(args, "video_file", MAX_VIDEO_SLOTS);
|
|
621
|
+
const imageUrls = uniqueStrings([
|
|
622
|
+
...collectStringArray(args.image_urls),
|
|
623
|
+
...collectStringArray(args.file_paths),
|
|
624
|
+
...collectStringArray(args.filePaths)
|
|
625
|
+
]);
|
|
626
|
+
const videoUrls = uniqueStrings(collectStringArray(args.video_urls));
|
|
627
|
+
const imageFiles = uniqueStrings(collectStringArray(args.image_files));
|
|
628
|
+
const videoFiles = uniqueStrings(collectStringArray(args.video_files));
|
|
629
|
+
assertLocalFilesExist(imageFiles);
|
|
630
|
+
assertLocalFilesExist(videoFiles);
|
|
631
|
+
const body = buildBaseVideoPayload(args, "omni_reference");
|
|
632
|
+
for (const [slot, url] of imageSlotUrls) {
|
|
633
|
+
body[`image_file_${slot}`] = url;
|
|
634
|
+
}
|
|
635
|
+
for (const [slot, url] of videoSlotUrls) {
|
|
636
|
+
body[`video_file_${slot}`] = url;
|
|
637
|
+
}
|
|
638
|
+
const occupiedImageSlots = new Set(imageSlotUrls.keys());
|
|
639
|
+
const occupiedVideoSlots = new Set(videoSlotUrls.keys());
|
|
640
|
+
appendUrlMaterials(body, occupiedImageSlots, imageUrls, "image_file", MAX_IMAGE_SLOTS, "image");
|
|
641
|
+
appendUrlMaterials(body, occupiedVideoSlots, videoUrls, "video_file", MAX_VIDEO_SLOTS, "video");
|
|
642
|
+
const uploadFiles = [];
|
|
643
|
+
appendFileMaterials(
|
|
644
|
+
uploadFiles,
|
|
645
|
+
occupiedImageSlots,
|
|
646
|
+
imageFiles,
|
|
647
|
+
"image_file",
|
|
648
|
+
MAX_IMAGE_SLOTS,
|
|
649
|
+
"image"
|
|
650
|
+
);
|
|
651
|
+
appendFileMaterials(
|
|
652
|
+
uploadFiles,
|
|
653
|
+
occupiedVideoSlots,
|
|
654
|
+
videoFiles,
|
|
655
|
+
"video_file",
|
|
656
|
+
MAX_VIDEO_SLOTS,
|
|
657
|
+
"video"
|
|
658
|
+
);
|
|
659
|
+
return { body, uploadFiles };
|
|
660
|
+
}
|
|
661
|
+
function registerGenerateVideoOmniTool({ server, config, client }) {
|
|
662
|
+
registerSafeTool(
|
|
663
|
+
server,
|
|
664
|
+
"generate_video_omni",
|
|
665
|
+
{
|
|
666
|
+
title: "Generate Video Omni",
|
|
667
|
+
description: "Generate omni_reference video with URL and local-file materials",
|
|
668
|
+
inputSchema: generateVideoOmniInputSchema
|
|
669
|
+
},
|
|
670
|
+
async (args) => {
|
|
671
|
+
assertRunConfirm(config, args.confirm);
|
|
672
|
+
const { body, uploadFiles } = normalizeOmniPayload(args);
|
|
673
|
+
return client.generateVideoOmni(body, { token: args.token }, uploadFiles);
|
|
674
|
+
}
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// src/mcp/tools/generate-video.ts
|
|
679
|
+
function registerGenerateVideoTool({ server, config, client }) {
|
|
680
|
+
registerSafeTool(
|
|
681
|
+
server,
|
|
682
|
+
"generate_video_flf",
|
|
683
|
+
{
|
|
684
|
+
title: "Generate Video FLF",
|
|
685
|
+
description: "Generate video for first_last_frames workflow only",
|
|
686
|
+
inputSchema: generateVideoInputSchema
|
|
687
|
+
},
|
|
688
|
+
async (args) => {
|
|
689
|
+
assertRunConfirm(config, args.confirm);
|
|
690
|
+
return client.generateVideo(
|
|
691
|
+
buildBaseVideoPayload(args, "first_last_frames"),
|
|
692
|
+
{ token: args.token }
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// src/mcp/tools/get-task.ts
|
|
699
|
+
function registerGetTaskTool({ server, client }) {
|
|
700
|
+
registerSafeTool(
|
|
701
|
+
server,
|
|
702
|
+
"get_task",
|
|
703
|
+
{
|
|
704
|
+
title: "Get Task",
|
|
705
|
+
description: "Get image/video task status by task_id",
|
|
706
|
+
inputSchema: getTaskInputSchema,
|
|
707
|
+
annotations: { readOnlyHint: true }
|
|
708
|
+
},
|
|
709
|
+
async (args) => client.getTask(
|
|
710
|
+
args.task_id,
|
|
711
|
+
{ token: args.token },
|
|
712
|
+
{
|
|
713
|
+
type: args.type,
|
|
714
|
+
response_format: args.response_format
|
|
715
|
+
}
|
|
716
|
+
)
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// src/mcp/tools/health-check.ts
|
|
721
|
+
function registerHealthCheckTool({ server, client }) {
|
|
722
|
+
registerSafeTool(
|
|
723
|
+
server,
|
|
724
|
+
"health_check",
|
|
725
|
+
{
|
|
726
|
+
title: "Health Check",
|
|
727
|
+
description: "Check jimeng-cli health endpoint",
|
|
728
|
+
inputSchema: healthCheckInputSchema
|
|
729
|
+
},
|
|
730
|
+
async () => {
|
|
731
|
+
const startedAt = Date.now();
|
|
732
|
+
const raw = await client.healthCheck();
|
|
733
|
+
return {
|
|
734
|
+
ok: true,
|
|
735
|
+
latencyMs: Date.now() - startedAt,
|
|
736
|
+
raw
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// src/mcp/tools/list-models.ts
|
|
743
|
+
function registerListModelsTool({ server, client }) {
|
|
744
|
+
registerSafeTool(
|
|
745
|
+
server,
|
|
746
|
+
"list_models",
|
|
747
|
+
{
|
|
748
|
+
title: "List Models",
|
|
749
|
+
description: "Get available models from jimeng-cli",
|
|
750
|
+
inputSchema: listModelsInputSchema,
|
|
751
|
+
annotations: { readOnlyHint: true }
|
|
752
|
+
},
|
|
753
|
+
async ({ token }) => client.listModels({ token })
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// src/mcp/tools/wait-task.ts
|
|
758
|
+
function registerWaitTaskTool({ server, client }) {
|
|
759
|
+
registerSafeTool(
|
|
760
|
+
server,
|
|
761
|
+
"wait_task",
|
|
762
|
+
{
|
|
763
|
+
title: "Wait Task",
|
|
764
|
+
description: "Wait for image/video task completion by task_id",
|
|
765
|
+
inputSchema: waitTaskInputSchema
|
|
766
|
+
},
|
|
767
|
+
async (args) => client.waitTask(
|
|
768
|
+
args.task_id,
|
|
769
|
+
{
|
|
770
|
+
type: args.type,
|
|
771
|
+
response_format: args.response_format,
|
|
772
|
+
wait_timeout_seconds: args.wait_timeout_seconds,
|
|
773
|
+
poll_interval_ms: args.poll_interval_ms
|
|
774
|
+
},
|
|
775
|
+
{ token: args.token }
|
|
776
|
+
)
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// src/mcp/tools/manifest.ts
|
|
781
|
+
var MCP_TOOL_MANIFEST = [
|
|
782
|
+
{ id: "health_check", register: registerHealthCheckTool },
|
|
783
|
+
{ id: "list_models", register: registerListModelsTool },
|
|
784
|
+
{ id: "get_task", register: registerGetTaskTool },
|
|
785
|
+
{ id: "wait_task", register: registerWaitTaskTool },
|
|
786
|
+
{ id: "generate_image", register: registerGenerateImageTool },
|
|
787
|
+
{ id: "edit_image", isAdvanced: true, register: registerEditImageTool },
|
|
788
|
+
{ id: "generate_video_flf", isAdvanced: true, register: registerGenerateVideoTool },
|
|
789
|
+
{ id: "generate_video_omni", isAdvanced: true, register: registerGenerateVideoOmniTool }
|
|
790
|
+
];
|
|
791
|
+
|
|
792
|
+
// src/mcp/tools/index.ts
|
|
793
|
+
function registerMcpTools(deps) {
|
|
794
|
+
for (const item of MCP_TOOL_MANIFEST) {
|
|
795
|
+
if (item.isAdvanced && !deps.config.enableAdvancedTools) continue;
|
|
796
|
+
item.register(deps);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// src/mcp/server.ts
|
|
801
|
+
function createJimengMcpServer(config) {
|
|
802
|
+
const server = new McpServer({
|
|
803
|
+
name: "jimeng-cli-mcp",
|
|
804
|
+
version: environment_default.package.version || "1.0.0"
|
|
805
|
+
});
|
|
806
|
+
const client = new JimengApiClient(config);
|
|
807
|
+
registerMcpTools({ server, config, client });
|
|
808
|
+
return server;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// src/mcp/index.ts
|
|
812
|
+
async function main() {
|
|
813
|
+
const config = loadMcpConfig();
|
|
814
|
+
const server = createJimengMcpServer(config);
|
|
815
|
+
const transport = new StdioServerTransport();
|
|
816
|
+
process.on("SIGINT", async () => {
|
|
817
|
+
await server.close();
|
|
818
|
+
process.exit(0);
|
|
819
|
+
});
|
|
820
|
+
process.on("SIGTERM", async () => {
|
|
821
|
+
await server.close();
|
|
822
|
+
process.exit(0);
|
|
823
|
+
});
|
|
824
|
+
await server.connect(transport);
|
|
825
|
+
console.error("[jimeng-cli-mcp] Server started on stdio transport");
|
|
826
|
+
}
|
|
827
|
+
main().catch((error) => {
|
|
828
|
+
console.error("[jimeng-cli-mcp] Failed to start MCP server:", error);
|
|
829
|
+
process.exit(1);
|
|
830
|
+
});
|
|
831
|
+
//# sourceMappingURL=index.js.map
|