cerevox 0.3.3 → 0.3.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.
- package/dist/mcp/servers/prompts/zerocut-guideline.md +354 -0
- package/dist/mcp/servers/zerocut.d.ts.map +1 -1
- package/dist/mcp/servers/zerocut.js +748 -293
- package/dist/mcp/servers/zerocut.js.map +1 -1
- package/dist/utils/videokit.d.ts.map +1 -1
- package/dist/utils/videokit.js +9 -3
- package/dist/utils/videokit.js.map +1 -1
- package/package.json +2 -2
|
@@ -17,6 +17,45 @@ const constants_1 = require("../../utils/constants");
|
|
|
17
17
|
const videokit_1 = require("../../utils/videokit");
|
|
18
18
|
const promises_1 = require("node:fs/promises");
|
|
19
19
|
const node_path_1 = require("node:path");
|
|
20
|
+
// 获取当前文件的目录路径
|
|
21
|
+
const __dirname = (0, node_path_1.dirname)(__filename);
|
|
22
|
+
// 错误处理工具函数
|
|
23
|
+
function createErrorResponse(error, operation) {
|
|
24
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
25
|
+
console.error(`[${operation}] Error:`, error);
|
|
26
|
+
return {
|
|
27
|
+
content: [
|
|
28
|
+
{
|
|
29
|
+
type: 'text',
|
|
30
|
+
text: JSON.stringify({
|
|
31
|
+
success: false,
|
|
32
|
+
error: errorMessage,
|
|
33
|
+
operation,
|
|
34
|
+
timestamp: new Date().toISOString(),
|
|
35
|
+
}),
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
// Session 状态检查
|
|
41
|
+
function validateSession(operation) {
|
|
42
|
+
if (!session) {
|
|
43
|
+
throw new Error(`Session not initialized. Please call 'zerocut-project-open' first before using ${operation}.`);
|
|
44
|
+
}
|
|
45
|
+
return session;
|
|
46
|
+
}
|
|
47
|
+
// 文件名验证
|
|
48
|
+
function validateFileName(fileName) {
|
|
49
|
+
if (!fileName || fileName.trim() === '') {
|
|
50
|
+
throw new Error('File name cannot be empty');
|
|
51
|
+
}
|
|
52
|
+
if (fileName.includes('..') ||
|
|
53
|
+
fileName.includes('/') ||
|
|
54
|
+
fileName.includes('\\')) {
|
|
55
|
+
throw new Error('Invalid file name: contains illegal characters');
|
|
56
|
+
}
|
|
57
|
+
return fileName.trim();
|
|
58
|
+
}
|
|
20
59
|
/* Configuration
|
|
21
60
|
{
|
|
22
61
|
"mcpServers": {
|
|
@@ -105,85 +144,266 @@ server.registerTool('zerocut-project-open', {
|
|
|
105
144
|
.describe('The path of the file to upload.'),
|
|
106
145
|
},
|
|
107
146
|
}, async ({ localDir }, context) => {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
content: [
|
|
128
|
-
{
|
|
129
|
-
type: 'text',
|
|
130
|
-
text: JSON.stringify({
|
|
131
|
-
sessionId: session.id,
|
|
132
|
-
workDir,
|
|
133
|
-
projectLocalDir,
|
|
134
|
-
materials,
|
|
135
|
-
}),
|
|
147
|
+
try {
|
|
148
|
+
// 检查是否已有活跃session
|
|
149
|
+
if (session) {
|
|
150
|
+
console.warn('Session already exists, closing previous session');
|
|
151
|
+
try {
|
|
152
|
+
await session.close();
|
|
153
|
+
}
|
|
154
|
+
catch (closeError) {
|
|
155
|
+
console.warn('Failed to close previous session:', closeError);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// 验证API密钥
|
|
159
|
+
if (!process.env.CEREVOX_API_KEY) {
|
|
160
|
+
throw new Error('CEREVOX_API_KEY environment variable is required');
|
|
161
|
+
}
|
|
162
|
+
console.log('Launching new Cerevox session...');
|
|
163
|
+
session = await cerevox.launch({
|
|
164
|
+
browser: {
|
|
165
|
+
liveview: true,
|
|
136
166
|
},
|
|
137
|
-
|
|
138
|
-
|
|
167
|
+
keepAliveMS: 10000,
|
|
168
|
+
timeoutMS: 3600000,
|
|
169
|
+
});
|
|
170
|
+
if (!session) {
|
|
171
|
+
throw new Error('Failed to create Cerevox session');
|
|
172
|
+
}
|
|
173
|
+
console.log('Initializing project...');
|
|
174
|
+
const workDir = await initProject(session);
|
|
175
|
+
projectLocalDir = (0, node_path_1.resolve)(process.env.ZEROCUT_PROJECT_CWD || process.cwd(), localDir || '.');
|
|
176
|
+
const syncDir = (0, node_path_1.resolve)(projectLocalDir, 'materials');
|
|
177
|
+
try {
|
|
178
|
+
await (0, promises_1.mkdir)(syncDir, { recursive: true });
|
|
179
|
+
}
|
|
180
|
+
catch (mkdirError) {
|
|
181
|
+
console.warn('Failed to create materials directory:', mkdirError);
|
|
182
|
+
// 继续执行,可能目录已存在
|
|
183
|
+
}
|
|
184
|
+
let materials = [];
|
|
185
|
+
try {
|
|
186
|
+
materials = await listFiles(syncDir);
|
|
187
|
+
}
|
|
188
|
+
catch (listError) {
|
|
189
|
+
console.warn('Failed to list materials:', listError);
|
|
190
|
+
materials = [];
|
|
191
|
+
}
|
|
192
|
+
const files = session.files;
|
|
193
|
+
let progress = 0;
|
|
194
|
+
const uploadErrors = [];
|
|
195
|
+
for (const material of materials) {
|
|
196
|
+
try {
|
|
197
|
+
await files.upload(material, `${workDir}/materials/${(0, node_path_1.basename)(material)}`);
|
|
198
|
+
await sendProgress(context, ++progress, materials.length, material);
|
|
199
|
+
}
|
|
200
|
+
catch (uploadError) {
|
|
201
|
+
const errorMsg = `Failed to upload ${material}: ${uploadError}`;
|
|
202
|
+
console.error(errorMsg);
|
|
203
|
+
uploadErrors.push(errorMsg);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const result = {
|
|
207
|
+
success: true,
|
|
208
|
+
sessionId: session.id,
|
|
209
|
+
workDir,
|
|
210
|
+
projectLocalDir,
|
|
211
|
+
materials,
|
|
212
|
+
uploadErrors: uploadErrors.length > 0 ? uploadErrors : undefined,
|
|
213
|
+
};
|
|
214
|
+
return {
|
|
215
|
+
content: [
|
|
216
|
+
{
|
|
217
|
+
type: 'text',
|
|
218
|
+
text: JSON.stringify(result),
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
// 不自动关闭session,让agent根据异常信息自行处理
|
|
225
|
+
return createErrorResponse(error, 'zerocut-project-open');
|
|
226
|
+
}
|
|
139
227
|
});
|
|
140
228
|
server.registerTool('zerocut-project-close', {
|
|
141
229
|
title: 'Close Project',
|
|
142
230
|
description: 'Close the current Cerevox session and release all resources.',
|
|
143
231
|
inputSchema: {},
|
|
144
232
|
}, async () => {
|
|
145
|
-
|
|
146
|
-
|
|
233
|
+
try {
|
|
234
|
+
if (session) {
|
|
235
|
+
console.log('Closing Cerevox session...');
|
|
236
|
+
await session.close();
|
|
237
|
+
session = null;
|
|
238
|
+
console.log('Session closed successfully');
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
console.warn('No active session to close');
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
content: [
|
|
245
|
+
{
|
|
246
|
+
type: 'text',
|
|
247
|
+
text: JSON.stringify({
|
|
248
|
+
success: true,
|
|
249
|
+
message: 'Project closed successfully.',
|
|
250
|
+
timestamp: new Date().toISOString(),
|
|
251
|
+
}),
|
|
252
|
+
},
|
|
253
|
+
],
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
// 即使关闭失败,也要清理session引用
|
|
147
258
|
session = null;
|
|
259
|
+
return createErrorResponse(error, 'zerocut-project-close');
|
|
148
260
|
}
|
|
149
|
-
return {
|
|
150
|
-
content: [{ type: 'text', text: 'Cerevox session closed' }],
|
|
151
|
-
};
|
|
152
261
|
});
|
|
153
262
|
// 将完成的成品下载到本地
|
|
263
|
+
// server.registerTool(
|
|
264
|
+
// 'download-outputs',
|
|
265
|
+
// {
|
|
266
|
+
// title: 'Download Outputs',
|
|
267
|
+
// description: 'Download the output files from the server.',
|
|
268
|
+
// inputSchema: {},
|
|
269
|
+
// },
|
|
270
|
+
// async (_, context) => {
|
|
271
|
+
// const terminal = session!.terminal;
|
|
272
|
+
// const workDir = `/home/user/cerevox-zerocut/projects/${terminal.id}/output`;
|
|
273
|
+
// const outputDir = resolve(
|
|
274
|
+
// process.env.ZEROCUT_PROJECT_CWD || process.cwd(),
|
|
275
|
+
// projectLocalDir,
|
|
276
|
+
// 'output'
|
|
277
|
+
// );
|
|
278
|
+
// await mkdir(outputDir, { recursive: true });
|
|
279
|
+
// let progress = 0;
|
|
280
|
+
// const outputs = (await session?.files.listFiles(workDir)) || [];
|
|
281
|
+
// const files = session!.files;
|
|
282
|
+
// for (const output of outputs) {
|
|
283
|
+
// await files.download(`${workDir}/${output}`, `${outputDir}/${output}`);
|
|
284
|
+
// sendProgress(context, ++progress, outputs.length, output);
|
|
285
|
+
// }
|
|
286
|
+
// return {
|
|
287
|
+
// content: [
|
|
288
|
+
// { type: 'text', text: JSON.stringify({ success: true, outputs }) },
|
|
289
|
+
// ],
|
|
290
|
+
// };
|
|
291
|
+
// }
|
|
292
|
+
// );
|
|
154
293
|
server.registerTool('download-outputs', {
|
|
155
|
-
title: 'Download Outputs',
|
|
294
|
+
title: 'Download Outputs from HTTP',
|
|
156
295
|
description: 'Download the output files from the server.',
|
|
157
296
|
inputSchema: {},
|
|
158
297
|
}, async (_, context) => {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
298
|
+
try {
|
|
299
|
+
// 验证session状态
|
|
300
|
+
const currentSession = validateSession('download-outputs');
|
|
301
|
+
console.log('Starting download outputs process...');
|
|
302
|
+
const terminal = currentSession.terminal;
|
|
303
|
+
if (!terminal) {
|
|
304
|
+
throw new Error('Terminal not available in current session');
|
|
305
|
+
}
|
|
306
|
+
const workDir = `/home/user/cerevox-zerocut/projects/${terminal.id}/output`;
|
|
307
|
+
const host = currentSession.getHost();
|
|
308
|
+
if (!host) {
|
|
309
|
+
throw new Error('Host information not available from session');
|
|
310
|
+
}
|
|
311
|
+
console.log(`Listing output files from: ${workDir}`);
|
|
312
|
+
let outputs = [];
|
|
313
|
+
try {
|
|
314
|
+
outputs = (await currentSession.files.listFiles(workDir)) || [];
|
|
315
|
+
}
|
|
316
|
+
catch (listError) {
|
|
317
|
+
console.warn('Failed to list output files:', listError);
|
|
318
|
+
outputs = [];
|
|
319
|
+
}
|
|
320
|
+
if (outputs.length === 0) {
|
|
321
|
+
console.log('No output files found');
|
|
322
|
+
return {
|
|
323
|
+
content: [
|
|
324
|
+
{
|
|
325
|
+
type: 'text',
|
|
326
|
+
text: JSON.stringify({
|
|
327
|
+
success: true,
|
|
328
|
+
status: 'no_files',
|
|
329
|
+
message: 'No output files found to download',
|
|
330
|
+
sources: [],
|
|
331
|
+
timestamp: new Date().toISOString(),
|
|
332
|
+
}),
|
|
333
|
+
},
|
|
334
|
+
],
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
console.log(`Found ${outputs.length} output files`);
|
|
338
|
+
const publicDir = `/home/user/public/zerocut`;
|
|
339
|
+
try {
|
|
340
|
+
const copyCommand = `mkdir -p ${publicDir} && cp -R ${workDir} ${publicDir}`;
|
|
341
|
+
console.log(`Executing: ${copyCommand}`);
|
|
342
|
+
const copyResult = await terminal.run(copyCommand);
|
|
343
|
+
await copyResult.end();
|
|
344
|
+
console.log('Files copied to public directory successfully');
|
|
345
|
+
}
|
|
346
|
+
catch (copyError) {
|
|
347
|
+
console.error('Failed to copy files to public directory:', copyError);
|
|
348
|
+
throw new Error(`Failed to prepare files for download: ${copyError}`);
|
|
349
|
+
}
|
|
350
|
+
let progress = 0;
|
|
351
|
+
const downloadErrors = [];
|
|
352
|
+
const successfulDownloads = [];
|
|
353
|
+
const outputDir = (0, node_path_1.resolve)(process.env.ZEROCUT_PROJECT_CWD || process.cwd(), projectLocalDir, 'output');
|
|
354
|
+
await (0, promises_1.mkdir)(outputDir, { recursive: true });
|
|
355
|
+
const promises = outputs.map(async (output) => {
|
|
356
|
+
try {
|
|
357
|
+
const url = `https://${host}/public/zerocut/output/${output}`;
|
|
358
|
+
console.log(`Downloading: ${url}`);
|
|
359
|
+
const res = await fetch(url);
|
|
360
|
+
if (!res.ok) {
|
|
361
|
+
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
|
362
|
+
}
|
|
363
|
+
const content = await res.arrayBuffer();
|
|
364
|
+
const buffer = Buffer.from(content);
|
|
365
|
+
await (0, promises_1.writeFile)(`${outputDir}/${output}`, buffer);
|
|
366
|
+
try {
|
|
367
|
+
await sendProgress(context, ++progress, outputs.length, output);
|
|
368
|
+
}
|
|
369
|
+
catch (progressError) {
|
|
370
|
+
console.warn('Failed to send progress update:', progressError);
|
|
371
|
+
}
|
|
372
|
+
successfulDownloads.push(url);
|
|
373
|
+
return url;
|
|
374
|
+
}
|
|
375
|
+
catch (downloadError) {
|
|
376
|
+
const errorMsg = `Failed to download ${output}: ${downloadError}`;
|
|
377
|
+
console.error(errorMsg);
|
|
378
|
+
downloadErrors.push(errorMsg);
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
const results = await Promise.all(promises);
|
|
383
|
+
const sources = results.filter(url => url !== null);
|
|
384
|
+
const result = {
|
|
385
|
+
success: downloadErrors.length === 0,
|
|
386
|
+
status: 'downloaded',
|
|
387
|
+
sources,
|
|
388
|
+
totalFiles: outputs.length,
|
|
389
|
+
successfulDownloads: sources.length,
|
|
390
|
+
failedDownloads: downloadErrors.length,
|
|
391
|
+
downloadErrors: downloadErrors.length > 0 ? downloadErrors : undefined,
|
|
392
|
+
timestamp: new Date().toISOString(),
|
|
393
|
+
};
|
|
394
|
+
console.log(`Download completed: ${sources.length}/${outputs.length} files successful`);
|
|
395
|
+
return {
|
|
396
|
+
content: [
|
|
397
|
+
{
|
|
398
|
+
type: 'text',
|
|
399
|
+
text: JSON.stringify(result),
|
|
400
|
+
},
|
|
401
|
+
],
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
catch (error) {
|
|
405
|
+
return createErrorResponse(error, 'download-outputs');
|
|
169
406
|
}
|
|
170
|
-
// await session!.files.syncDownloadsDirectory(
|
|
171
|
-
// outputDir,
|
|
172
|
-
// workDir,
|
|
173
|
-
// async metaData => {
|
|
174
|
-
// await sendProgress(
|
|
175
|
-
// context,
|
|
176
|
-
// ++progress,
|
|
177
|
-
// outputs.length,
|
|
178
|
-
// JSON.stringify(metaData)
|
|
179
|
-
// );
|
|
180
|
-
// }
|
|
181
|
-
// );
|
|
182
|
-
return {
|
|
183
|
-
content: [
|
|
184
|
-
{ type: 'text', text: JSON.stringify({ success: true, outputs }) },
|
|
185
|
-
],
|
|
186
|
-
};
|
|
187
407
|
});
|
|
188
408
|
// 列出项目下的所有文件
|
|
189
409
|
server.registerTool('list-project-files', {
|
|
@@ -191,67 +411,63 @@ server.registerTool('list-project-files', {
|
|
|
191
411
|
description: 'List all files in the materials directory.',
|
|
192
412
|
inputSchema: {},
|
|
193
413
|
}, async () => {
|
|
194
|
-
|
|
195
|
-
|
|
414
|
+
try {
|
|
415
|
+
// 验证session状态
|
|
416
|
+
const currentSession = validateSession('list-project-files');
|
|
417
|
+
console.log('Listing project files...');
|
|
418
|
+
const terminal = currentSession.terminal;
|
|
419
|
+
if (!terminal) {
|
|
420
|
+
throw new Error('Terminal not available in current session');
|
|
421
|
+
}
|
|
422
|
+
let cwd;
|
|
423
|
+
try {
|
|
424
|
+
cwd = await terminal.getCwd();
|
|
425
|
+
}
|
|
426
|
+
catch (cwdError) {
|
|
427
|
+
console.error('Failed to get current working directory:', cwdError);
|
|
428
|
+
throw new Error('Failed to get current working directory');
|
|
429
|
+
}
|
|
430
|
+
console.log(`Current working directory: ${cwd}`);
|
|
431
|
+
// 安全地列出各目录文件,失败时返回空数组
|
|
432
|
+
const listFilesWithFallback = async (path, dirName) => {
|
|
433
|
+
try {
|
|
434
|
+
const files = await currentSession.files.listFiles(path);
|
|
435
|
+
console.log(`Found ${files?.length || 0} files in ${dirName}`);
|
|
436
|
+
return files || [];
|
|
437
|
+
}
|
|
438
|
+
catch (error) {
|
|
439
|
+
console.warn(`Failed to list files in ${dirName} (${path}):`, error);
|
|
440
|
+
return [];
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
const [rootFiles, materialsFiles, outputFiles] = await Promise.all([
|
|
444
|
+
listFilesWithFallback(cwd, 'root'),
|
|
445
|
+
listFilesWithFallback(`${cwd}/materials`, 'materials'),
|
|
446
|
+
listFilesWithFallback(`${cwd}/output`, 'output'),
|
|
447
|
+
]);
|
|
448
|
+
const result = {
|
|
449
|
+
success: true,
|
|
450
|
+
cwd,
|
|
451
|
+
root: rootFiles,
|
|
452
|
+
materials: materialsFiles,
|
|
453
|
+
output: outputFiles,
|
|
454
|
+
totalFiles: rootFiles.length + materialsFiles.length + outputFiles.length,
|
|
455
|
+
timestamp: new Date().toISOString(),
|
|
456
|
+
};
|
|
457
|
+
console.log(`Total files found: ${result.totalFiles}`);
|
|
196
458
|
return {
|
|
197
|
-
content: [
|
|
459
|
+
content: [
|
|
460
|
+
{
|
|
461
|
+
type: 'text',
|
|
462
|
+
text: JSON.stringify(result),
|
|
463
|
+
},
|
|
464
|
+
],
|
|
198
465
|
};
|
|
199
466
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const outputFiles = await session?.files.listFiles(`${cwd}/output`);
|
|
204
|
-
return {
|
|
205
|
-
content: [
|
|
206
|
-
{
|
|
207
|
-
type: 'text',
|
|
208
|
-
text: JSON.stringify({
|
|
209
|
-
root: rootFiles,
|
|
210
|
-
materials: materialsFiles,
|
|
211
|
-
output: outputFiles,
|
|
212
|
-
}),
|
|
213
|
-
},
|
|
214
|
-
],
|
|
215
|
-
};
|
|
467
|
+
catch (error) {
|
|
468
|
+
return createErrorResponse(error, 'list-project-files');
|
|
469
|
+
}
|
|
216
470
|
});
|
|
217
|
-
// server.registerTool(
|
|
218
|
-
// 'read-file',
|
|
219
|
-
// {
|
|
220
|
-
// title: 'Read File',
|
|
221
|
-
// description: 'Read the content of a file.',
|
|
222
|
-
// inputSchema: {
|
|
223
|
-
// path: z.string().describe('The path of the file to read.'),
|
|
224
|
-
// },
|
|
225
|
-
// },
|
|
226
|
-
// async ({ path }) => {
|
|
227
|
-
// const files = session!.files;
|
|
228
|
-
// const content: string = await files.read(path, { encoding: 'utf8' });
|
|
229
|
-
// return {
|
|
230
|
-
// content: [{ type: 'text', text: content }],
|
|
231
|
-
// };
|
|
232
|
-
// }
|
|
233
|
-
// );
|
|
234
|
-
// server.registerTool(
|
|
235
|
-
// 'write-file',
|
|
236
|
-
// {
|
|
237
|
-
// title: 'Write File',
|
|
238
|
-
// description: 'Write the content to a file.',
|
|
239
|
-
// inputSchema: {
|
|
240
|
-
// path: z.string().describe('The path of the file to write.'),
|
|
241
|
-
// content: z.string().describe('The content to write.'),
|
|
242
|
-
// },
|
|
243
|
-
// },
|
|
244
|
-
// async ({ path, content }) => {
|
|
245
|
-
// const files = session!.files;
|
|
246
|
-
// await files.write(path, content);
|
|
247
|
-
// assetsCount++;
|
|
248
|
-
// return {
|
|
249
|
-
// content: [
|
|
250
|
-
// { type: 'text', text: JSON.stringify({ path, success: true }) },
|
|
251
|
-
// ],
|
|
252
|
-
// };
|
|
253
|
-
// }
|
|
254
|
-
// );
|
|
255
471
|
server.registerTool('search-context', {
|
|
256
472
|
title: 'Search Context',
|
|
257
473
|
description: 'Search the context.',
|
|
@@ -299,22 +515,59 @@ server.registerTool('generate-image', {
|
|
|
299
515
|
saveToFileName: zod_1.z.string().describe('The filename to save.'),
|
|
300
516
|
},
|
|
301
517
|
}, async ({ prompt, size, saveToFileName }) => {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
518
|
+
try {
|
|
519
|
+
// 验证session状态
|
|
520
|
+
const currentSession = validateSession('generate-image');
|
|
521
|
+
const validatedFileName = validateFileName(saveToFileName);
|
|
522
|
+
console.log(`Generating image with prompt: ${prompt.substring(0, 100)}...`);
|
|
523
|
+
const ai = currentSession.ai;
|
|
524
|
+
const res = await ai.generateImage({
|
|
525
|
+
prompt: prompt.trim(),
|
|
526
|
+
size,
|
|
527
|
+
});
|
|
528
|
+
if (!res) {
|
|
529
|
+
throw new Error('Failed to generate image: no response from AI service');
|
|
530
|
+
}
|
|
531
|
+
if (res.url) {
|
|
532
|
+
console.log('Image generated successfully, saving to materials...');
|
|
533
|
+
const uri = await saveMertial(currentSession, res.url, validatedFileName);
|
|
534
|
+
const result = {
|
|
535
|
+
success: true,
|
|
536
|
+
source: res.url,
|
|
537
|
+
uri,
|
|
538
|
+
prompt: prompt.substring(0, 100),
|
|
539
|
+
size,
|
|
540
|
+
timestamp: new Date().toISOString(),
|
|
541
|
+
};
|
|
542
|
+
return {
|
|
543
|
+
content: [
|
|
544
|
+
{
|
|
545
|
+
type: 'text',
|
|
546
|
+
text: JSON.stringify(result),
|
|
547
|
+
},
|
|
548
|
+
],
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
console.warn('Image generation completed but no URL returned');
|
|
553
|
+
return {
|
|
554
|
+
content: [
|
|
555
|
+
{
|
|
556
|
+
type: 'text',
|
|
557
|
+
text: JSON.stringify({
|
|
558
|
+
success: false,
|
|
559
|
+
error: 'No image URL returned from AI service',
|
|
560
|
+
response: res,
|
|
561
|
+
timestamp: new Date().toISOString(),
|
|
562
|
+
}),
|
|
563
|
+
},
|
|
564
|
+
],
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
catch (error) {
|
|
569
|
+
return createErrorResponse(error, 'generate-image');
|
|
314
570
|
}
|
|
315
|
-
return {
|
|
316
|
-
content: [{ type: 'text', text: JSON.stringify(res) }],
|
|
317
|
-
};
|
|
318
571
|
});
|
|
319
572
|
server.registerTool('generate-video', {
|
|
320
573
|
title: 'Generate Video',
|
|
@@ -335,38 +588,73 @@ server.registerTool('generate-video', {
|
|
|
335
588
|
.describe('The end frame image URL.'),
|
|
336
589
|
},
|
|
337
590
|
}, async ({ prompt, saveToFileName, start_frame, end_frame, duration }, context) => {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
{
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
591
|
+
try {
|
|
592
|
+
// 验证session状态
|
|
593
|
+
const currentSession = validateSession('generate-video');
|
|
594
|
+
const validatedFileName = validateFileName(saveToFileName);
|
|
595
|
+
console.log(`Generating video with prompt: ${prompt.substring(0, 100)}...`);
|
|
596
|
+
const ai = currentSession.ai;
|
|
597
|
+
let progress = 0;
|
|
598
|
+
const res = await ai.framesToVideo({
|
|
599
|
+
prompt: prompt.trim(),
|
|
600
|
+
start_frame,
|
|
601
|
+
end_frame,
|
|
602
|
+
duration,
|
|
603
|
+
resolution: '720p',
|
|
604
|
+
onProgress: async (metaData) => {
|
|
605
|
+
try {
|
|
606
|
+
await sendProgress(context, ++progress, undefined, JSON.stringify(metaData));
|
|
607
|
+
}
|
|
608
|
+
catch (progressError) {
|
|
609
|
+
console.warn('Failed to send progress update:', progressError);
|
|
610
|
+
}
|
|
611
|
+
},
|
|
612
|
+
});
|
|
613
|
+
if (!res) {
|
|
614
|
+
throw new Error('Failed to generate video: no response from AI service');
|
|
615
|
+
}
|
|
616
|
+
if (res.url) {
|
|
617
|
+
console.log('Video generated successfully, saving to materials...');
|
|
618
|
+
const uri = await saveMertial(currentSession, res.url, validatedFileName);
|
|
619
|
+
const { url, duration: videoDuration, ...opts } = res;
|
|
620
|
+
const result = {
|
|
621
|
+
success: true,
|
|
622
|
+
source: url,
|
|
623
|
+
uri,
|
|
624
|
+
durationMs: Math.floor((videoDuration || duration) * 1000),
|
|
625
|
+
prompt: prompt.substring(0, 100),
|
|
626
|
+
timestamp: new Date().toISOString(),
|
|
627
|
+
...opts,
|
|
628
|
+
};
|
|
629
|
+
return {
|
|
630
|
+
content: [
|
|
631
|
+
{
|
|
632
|
+
type: 'text',
|
|
633
|
+
text: JSON.stringify(result),
|
|
634
|
+
},
|
|
635
|
+
],
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
console.warn('Video generation completed but no URL returned');
|
|
640
|
+
return {
|
|
641
|
+
content: [
|
|
642
|
+
{
|
|
643
|
+
type: 'text',
|
|
644
|
+
text: JSON.stringify({
|
|
645
|
+
success: false,
|
|
646
|
+
error: 'No video URL returned from AI service',
|
|
647
|
+
response: res,
|
|
648
|
+
timestamp: new Date().toISOString(),
|
|
649
|
+
}),
|
|
650
|
+
},
|
|
651
|
+
],
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
catch (error) {
|
|
656
|
+
return createErrorResponse(error, 'generate-video');
|
|
366
657
|
}
|
|
367
|
-
return {
|
|
368
|
-
content: [{ type: 'text', text: JSON.stringify(res) }],
|
|
369
|
-
};
|
|
370
658
|
});
|
|
371
659
|
server.registerTool('generate-video-fallback', {
|
|
372
660
|
title: 'Generate Ken Burns Motion as Video Fallback',
|
|
@@ -394,35 +682,94 @@ server.registerTool('generate-video-fallback', {
|
|
|
394
682
|
saveToFileName: zod_1.z.string().describe('The filename to save.'),
|
|
395
683
|
},
|
|
396
684
|
}, async ({ frame_index, image_path, duration, size, saveToFileName }) => {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
685
|
+
try {
|
|
686
|
+
// 验证session状态
|
|
687
|
+
const currentSession = validateSession('generate-video-fallback');
|
|
688
|
+
const validatedFileName = validateFileName(saveToFileName);
|
|
689
|
+
console.log(`Generating Ken Burns motion for frame ${frame_index}: ${image_path} (${duration}s, ${size})`);
|
|
690
|
+
const files = currentSession.files;
|
|
691
|
+
const terminal = currentSession.terminal;
|
|
692
|
+
if (!terminal) {
|
|
693
|
+
throw new Error('Terminal not available in current session');
|
|
694
|
+
}
|
|
695
|
+
const saveToPath = `/home/user/cerevox-zerocut/projects/${terminal.id}/materials/${validatedFileName}`;
|
|
696
|
+
const saveLocalPath = (0, node_path_1.resolve)(projectLocalDir, 'materials', validatedFileName);
|
|
697
|
+
// 解析尺寸参数
|
|
698
|
+
const sizeArray = size.split('x');
|
|
699
|
+
if (sizeArray.length !== 2) {
|
|
700
|
+
throw new Error(`Invalid size format: ${size}. Expected format: WIDTHxHEIGHT`);
|
|
701
|
+
}
|
|
702
|
+
const [widthStr, heightStr] = sizeArray;
|
|
703
|
+
const width = Number(widthStr);
|
|
704
|
+
const height = Number(heightStr);
|
|
705
|
+
if (isNaN(width) || isNaN(height) || width <= 0 || height <= 0) {
|
|
706
|
+
throw new Error(`Invalid dimensions: ${width}x${height}. Both width and height must be positive numbers`);
|
|
707
|
+
}
|
|
708
|
+
console.log(`Compiling Ken Burns motion command...`);
|
|
709
|
+
let command;
|
|
710
|
+
try {
|
|
711
|
+
command = await (0, videokit_1.compileKenBurnsMotion)(frame_index, image_path, duration, {
|
|
712
|
+
output: saveToPath,
|
|
713
|
+
width,
|
|
714
|
+
height,
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
catch (compileError) {
|
|
718
|
+
console.error('Failed to compile Ken Burns motion command:', compileError);
|
|
719
|
+
throw new Error(`Failed to compile Ken Burns motion: ${compileError}`);
|
|
720
|
+
}
|
|
721
|
+
console.log(`Executing FFmpeg command: ${command.substring(0, 100)}...`);
|
|
722
|
+
const res = await terminal.run(command);
|
|
723
|
+
const result = await res.json();
|
|
724
|
+
if (result.exitCode !== 0) {
|
|
725
|
+
console.error('FFmpeg command failed:', result);
|
|
726
|
+
return {
|
|
727
|
+
content: [
|
|
728
|
+
{
|
|
729
|
+
type: 'text',
|
|
730
|
+
text: JSON.stringify({
|
|
731
|
+
success: false,
|
|
732
|
+
error: 'Ken Burns motion generation failed',
|
|
733
|
+
exitCode: result.exitCode,
|
|
734
|
+
stderr: result.stderr,
|
|
735
|
+
command: command.substring(0, 200),
|
|
736
|
+
timestamp: new Date().toISOString(),
|
|
737
|
+
}),
|
|
738
|
+
},
|
|
739
|
+
],
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
console.log('Ken Burns motion generated successfully, downloading file...');
|
|
743
|
+
try {
|
|
744
|
+
await files.download(saveToPath, saveLocalPath);
|
|
745
|
+
}
|
|
746
|
+
catch (downloadError) {
|
|
747
|
+
console.warn('Failed to download file to local:', downloadError);
|
|
748
|
+
// 继续执行,因为远程文件已生成成功
|
|
749
|
+
}
|
|
750
|
+
const resultData = {
|
|
751
|
+
success: true,
|
|
752
|
+
uri: saveToPath,
|
|
753
|
+
durationMs: Math.floor(duration * 1000),
|
|
754
|
+
frameIndex: frame_index,
|
|
755
|
+
imagePath: image_path,
|
|
756
|
+
size,
|
|
757
|
+
dimensions: { width, height },
|
|
758
|
+
timestamp: new Date().toISOString(),
|
|
759
|
+
};
|
|
760
|
+
console.log(`Ken Burns motion completed: ${saveToPath}`);
|
|
410
761
|
return {
|
|
411
|
-
content: [
|
|
762
|
+
content: [
|
|
763
|
+
{
|
|
764
|
+
type: 'text',
|
|
765
|
+
text: JSON.stringify(resultData),
|
|
766
|
+
},
|
|
767
|
+
],
|
|
412
768
|
};
|
|
413
769
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
{
|
|
418
|
-
type: 'text',
|
|
419
|
-
text: JSON.stringify({
|
|
420
|
-
uri: saveToPath,
|
|
421
|
-
durationMs: Math.floor(duration * 1000),
|
|
422
|
-
}),
|
|
423
|
-
},
|
|
424
|
-
],
|
|
425
|
-
};
|
|
770
|
+
catch (error) {
|
|
771
|
+
return createErrorResponse(error, 'generate-video-fallback');
|
|
772
|
+
}
|
|
426
773
|
});
|
|
427
774
|
server.registerTool('generate-bgm', {
|
|
428
775
|
title: 'Generate BGM',
|
|
@@ -437,35 +784,71 @@ server.registerTool('generate-bgm', {
|
|
|
437
784
|
saveToFileName: zod_1.z.string().describe('The filename to save.'),
|
|
438
785
|
},
|
|
439
786
|
}, async ({ prompt, duration, saveToFileName }, context) => {
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
duration
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
{
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
787
|
+
try {
|
|
788
|
+
// 验证session状态
|
|
789
|
+
const currentSession = validateSession('generate-bgm');
|
|
790
|
+
const validatedFileName = validateFileName(saveToFileName);
|
|
791
|
+
console.log(`Generating BGM with prompt: ${prompt.substring(0, 100)}... (${duration}s)`);
|
|
792
|
+
const ai = currentSession.ai;
|
|
793
|
+
let progress = 0;
|
|
794
|
+
const res = await ai.generateBGM({
|
|
795
|
+
prompt: prompt.trim(),
|
|
796
|
+
duration,
|
|
797
|
+
onProgress: async (metaData) => {
|
|
798
|
+
try {
|
|
799
|
+
await sendProgress(context, metaData.Result?.Progress ?? ++progress, metaData.Result?.Progress ? 100 : undefined, JSON.stringify(metaData));
|
|
800
|
+
}
|
|
801
|
+
catch (progressError) {
|
|
802
|
+
console.warn('Failed to send progress update:', progressError);
|
|
803
|
+
}
|
|
804
|
+
},
|
|
805
|
+
});
|
|
806
|
+
if (!res) {
|
|
807
|
+
throw new Error('Failed to generate BGM: no response from AI service');
|
|
808
|
+
}
|
|
809
|
+
if (res.url) {
|
|
810
|
+
console.log('BGM generated successfully, saving to materials...');
|
|
811
|
+
const uri = await saveMertial(currentSession, res.url, validatedFileName);
|
|
812
|
+
const { url, duration: bgmDuration, ...opts } = res;
|
|
813
|
+
const result = {
|
|
814
|
+
success: true,
|
|
815
|
+
source: url,
|
|
816
|
+
uri,
|
|
817
|
+
durationMs: Math.floor((bgmDuration || duration) * 1000),
|
|
818
|
+
prompt: prompt.substring(0, 100),
|
|
819
|
+
requestedDuration: duration,
|
|
820
|
+
timestamp: new Date().toISOString(),
|
|
821
|
+
...opts,
|
|
822
|
+
};
|
|
823
|
+
return {
|
|
824
|
+
content: [
|
|
825
|
+
{
|
|
826
|
+
type: 'text',
|
|
827
|
+
text: JSON.stringify(result),
|
|
828
|
+
},
|
|
829
|
+
],
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
else {
|
|
833
|
+
console.warn('BGM generation completed but no URL returned');
|
|
834
|
+
return {
|
|
835
|
+
content: [
|
|
836
|
+
{
|
|
837
|
+
type: 'text',
|
|
838
|
+
text: JSON.stringify({
|
|
839
|
+
success: false,
|
|
840
|
+
error: 'No BGM URL returned from AI service',
|
|
841
|
+
response: res,
|
|
842
|
+
timestamp: new Date().toISOString(),
|
|
843
|
+
}),
|
|
844
|
+
},
|
|
845
|
+
],
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
catch (error) {
|
|
850
|
+
return createErrorResponse(error, 'generate-bgm');
|
|
465
851
|
}
|
|
466
|
-
return {
|
|
467
|
-
content: [{ type: 'text', text: JSON.stringify(res) }],
|
|
468
|
-
};
|
|
469
852
|
});
|
|
470
853
|
server.registerTool('generate-scene-tts', {
|
|
471
854
|
title: 'Generate Scene TTS',
|
|
@@ -501,32 +884,65 @@ server.registerTool('generate-scene-tts', {
|
|
|
501
884
|
.describe('适合作为视频配音的中英文音色(推荐15个)。来源于系统音色清单,偏中性、耐听,覆盖新闻/主持、企业宣传、科普解说、纪录片与生活方式等场景。'),
|
|
502
885
|
},
|
|
503
886
|
}, async ({ text, voiceName, saveToFileName, speed }) => {
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
speed
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
const
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
887
|
+
try {
|
|
888
|
+
// 验证session状态
|
|
889
|
+
const currentSession = validateSession('generate-scene-tts');
|
|
890
|
+
const validatedFileName = validateFileName(saveToFileName);
|
|
891
|
+
const finalSpeed = speed ?? 1;
|
|
892
|
+
console.log(`Generating TTS with voice: ${voiceName}, speed: ${finalSpeed}, text: ${text.substring(0, 100)}...`);
|
|
893
|
+
const ai = currentSession.ai;
|
|
894
|
+
const res = await ai.textToSpeech({
|
|
895
|
+
text: text.trim(),
|
|
896
|
+
voiceName,
|
|
897
|
+
speed: finalSpeed,
|
|
898
|
+
});
|
|
899
|
+
if (!res) {
|
|
900
|
+
throw new Error('Failed to generate TTS: no response from AI service');
|
|
901
|
+
}
|
|
902
|
+
if (res.url) {
|
|
903
|
+
console.log('TTS generated successfully, saving to materials...');
|
|
904
|
+
const uri = await saveMertial(currentSession, res.url, validatedFileName);
|
|
905
|
+
const { url, duration, ...opts } = res;
|
|
906
|
+
const result = {
|
|
907
|
+
success: true,
|
|
908
|
+
source: url,
|
|
909
|
+
uri,
|
|
910
|
+
durationMs: Math.floor((duration || 0) * 1000),
|
|
911
|
+
text: text.substring(0, 100),
|
|
912
|
+
voiceName,
|
|
913
|
+
speed: finalSpeed,
|
|
914
|
+
timestamp: new Date().toISOString(),
|
|
915
|
+
...opts,
|
|
916
|
+
};
|
|
917
|
+
return {
|
|
918
|
+
content: [
|
|
919
|
+
{
|
|
920
|
+
type: 'text',
|
|
921
|
+
text: JSON.stringify(result),
|
|
922
|
+
},
|
|
923
|
+
],
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
else {
|
|
927
|
+
console.warn('TTS generation completed but no URL returned');
|
|
928
|
+
return {
|
|
929
|
+
content: [
|
|
930
|
+
{
|
|
931
|
+
type: 'text',
|
|
932
|
+
text: JSON.stringify({
|
|
933
|
+
success: false,
|
|
934
|
+
error: 'No TTS URL returned from AI service',
|
|
935
|
+
response: res,
|
|
936
|
+
timestamp: new Date().toISOString(),
|
|
937
|
+
}),
|
|
938
|
+
},
|
|
939
|
+
],
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
catch (error) {
|
|
944
|
+
return createErrorResponse(error, 'generate-scene-tts');
|
|
526
945
|
}
|
|
527
|
-
return {
|
|
528
|
-
content: [{ type: 'text', text: JSON.stringify(res) }],
|
|
529
|
-
};
|
|
530
946
|
});
|
|
531
947
|
server.registerTool('compile-and-run', {
|
|
532
948
|
title: 'Compile And Run',
|
|
@@ -540,71 +956,89 @@ server.registerTool('compile-and-run', {
|
|
|
540
956
|
},
|
|
541
957
|
}, async ({ project, outputFileName }) => {
|
|
542
958
|
try {
|
|
543
|
-
//
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
|
|
959
|
+
// 验证session状态
|
|
960
|
+
const currentSession = validateSession('compile-and-run');
|
|
961
|
+
console.log('Starting video compilation and rendering...');
|
|
962
|
+
// 验证terminal可用性
|
|
963
|
+
const terminal = currentSession.terminal;
|
|
964
|
+
if (!terminal) {
|
|
965
|
+
throw new Error('Terminal not available in current session');
|
|
966
|
+
}
|
|
967
|
+
// 验证输出文件名安全性
|
|
968
|
+
const outFile = outputFileName || 'output.mp4';
|
|
969
|
+
const validatedFileName = validateFileName(outFile);
|
|
970
|
+
console.log(`Output file: ${validatedFileName}`);
|
|
971
|
+
// 构建工作目录路径
|
|
547
972
|
const workDir = `/home/user/cerevox-zerocut/projects/${terminal.id}`;
|
|
548
973
|
const outputDir = `${workDir}/output`;
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
const
|
|
552
|
-
//
|
|
553
|
-
validated.export
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
974
|
+
const outputPath = `${outputDir}/${validatedFileName}`;
|
|
975
|
+
// Project已经通过zVideoProject schema验证
|
|
976
|
+
const validated = { ...project };
|
|
977
|
+
// 更新导出配置
|
|
978
|
+
validated.export = {
|
|
979
|
+
...validated.export,
|
|
980
|
+
outFile: outputPath,
|
|
981
|
+
};
|
|
982
|
+
console.log('Compiling VideoProject to FFmpeg command...');
|
|
983
|
+
// 编译为FFmpeg命令
|
|
984
|
+
let compiled;
|
|
985
|
+
try {
|
|
986
|
+
compiled = (0, videokit_1.compileToFfmpeg)(validated, {
|
|
987
|
+
workingDir: outputDir,
|
|
988
|
+
subtitleStrategy: 'auto',
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
catch (compileError) {
|
|
992
|
+
console.error('Failed to compile VideoProject:', compileError);
|
|
993
|
+
throw new Error(`Failed to compile VideoProject: ${compileError}`);
|
|
994
|
+
}
|
|
995
|
+
console.log(`FFmpeg command generated (${compiled.cmd.length} chars)`);
|
|
996
|
+
console.log('FFmpeg Command:', compiled.cmd.substring(0, 200) + '...');
|
|
997
|
+
// 执行FFmpeg命令
|
|
998
|
+
console.log('Executing FFmpeg command...');
|
|
999
|
+
const result = await runFfmpeg(currentSession, compiled);
|
|
563
1000
|
if (result.exitCode === 0) {
|
|
1001
|
+
console.log('Video compilation completed successfully');
|
|
1002
|
+
const successResult = {
|
|
1003
|
+
success: true,
|
|
1004
|
+
outputPath,
|
|
1005
|
+
outputFileName: validatedFileName,
|
|
1006
|
+
command: compiled.cmd.substring(0, 500), // 限制命令长度
|
|
1007
|
+
message: 'Video compilation completed successfully',
|
|
1008
|
+
timestamp: new Date().toISOString(),
|
|
1009
|
+
};
|
|
564
1010
|
return {
|
|
565
1011
|
content: [
|
|
566
1012
|
{
|
|
567
1013
|
type: 'text',
|
|
568
|
-
text: JSON.stringify(
|
|
569
|
-
success: true,
|
|
570
|
-
outputPath,
|
|
571
|
-
command: compiled.cmd,
|
|
572
|
-
message: 'Video compilation completed successfully',
|
|
573
|
-
}),
|
|
1014
|
+
text: JSON.stringify(successResult),
|
|
574
1015
|
},
|
|
575
1016
|
],
|
|
576
1017
|
};
|
|
577
1018
|
}
|
|
578
1019
|
else {
|
|
1020
|
+
console.error(`FFmpeg failed with exit code: ${result.exitCode}`);
|
|
1021
|
+
const failureResult = {
|
|
1022
|
+
success: false,
|
|
1023
|
+
exitCode: result.exitCode,
|
|
1024
|
+
outputPath,
|
|
1025
|
+
command: compiled.cmd.substring(0, 500),
|
|
1026
|
+
stderr: result.stderr?.substring(0, 1000), // 限制错误输出长度
|
|
1027
|
+
message: `FFmpeg exited with code ${result.exitCode}`,
|
|
1028
|
+
timestamp: new Date().toISOString(),
|
|
1029
|
+
};
|
|
579
1030
|
return {
|
|
580
1031
|
content: [
|
|
581
1032
|
{
|
|
582
1033
|
type: 'text',
|
|
583
|
-
text: JSON.stringify(
|
|
584
|
-
success: false,
|
|
585
|
-
exitCode: result.exitCode,
|
|
586
|
-
command: compiled.cmd,
|
|
587
|
-
message: `FFmpeg exited with code ${result.exitCode}`,
|
|
588
|
-
}),
|
|
1034
|
+
text: JSON.stringify(failureResult),
|
|
589
1035
|
},
|
|
590
1036
|
],
|
|
591
1037
|
};
|
|
592
1038
|
}
|
|
593
1039
|
}
|
|
594
1040
|
catch (error) {
|
|
595
|
-
|
|
596
|
-
return {
|
|
597
|
-
content: [
|
|
598
|
-
{
|
|
599
|
-
type: 'text',
|
|
600
|
-
text: JSON.stringify({
|
|
601
|
-
success: false,
|
|
602
|
-
error: errorMessage,
|
|
603
|
-
message: 'Failed to compile and run project',
|
|
604
|
-
}),
|
|
605
|
-
},
|
|
606
|
-
],
|
|
607
|
-
};
|
|
1041
|
+
return createErrorResponse(error, 'compile-and-run');
|
|
608
1042
|
}
|
|
609
1043
|
});
|
|
610
1044
|
server.registerTool('get-video-project-schema', {
|
|
@@ -620,22 +1054,43 @@ server.registerTool('get-video-project-schema', {
|
|
|
620
1054
|
content: [
|
|
621
1055
|
{
|
|
622
1056
|
type: 'text',
|
|
623
|
-
text:
|
|
1057
|
+
text: JSON.stringify({
|
|
1058
|
+
success: true,
|
|
1059
|
+
schema,
|
|
1060
|
+
timestamp: new Date().toISOString(),
|
|
1061
|
+
}),
|
|
624
1062
|
},
|
|
625
1063
|
],
|
|
626
1064
|
};
|
|
627
1065
|
}
|
|
628
1066
|
catch (error) {
|
|
1067
|
+
return createErrorResponse(error, 'get-video-project-schema');
|
|
1068
|
+
}
|
|
1069
|
+
});
|
|
1070
|
+
// 注册 ZeroCut 指导规范 Prompt
|
|
1071
|
+
server.registerPrompt('zerocut-guideline', {
|
|
1072
|
+
title: 'ZeroCut 短视频创作指导规范',
|
|
1073
|
+
description: '专业的短视频创作 Agent 指导规范,包含完整的工作流程、工具说明和质量建议',
|
|
1074
|
+
}, async () => {
|
|
1075
|
+
try {
|
|
1076
|
+
const promptPath = (0, node_path_1.resolve)(__dirname, './prompts/zerocut-guideline.md');
|
|
1077
|
+
const promptContent = await (0, promises_1.readFile)(promptPath, 'utf-8');
|
|
629
1078
|
return {
|
|
630
|
-
|
|
1079
|
+
messages: [
|
|
631
1080
|
{
|
|
632
|
-
|
|
633
|
-
|
|
1081
|
+
role: 'user',
|
|
1082
|
+
content: {
|
|
1083
|
+
type: 'text',
|
|
1084
|
+
text: promptContent,
|
|
1085
|
+
},
|
|
634
1086
|
},
|
|
635
1087
|
],
|
|
636
|
-
isError: true,
|
|
637
1088
|
};
|
|
638
1089
|
}
|
|
1090
|
+
catch (error) {
|
|
1091
|
+
console.error('Failed to load zerocut-guideline prompt:', error);
|
|
1092
|
+
throw new Error(`Failed to load zerocut-guideline prompt: ${error}`);
|
|
1093
|
+
}
|
|
639
1094
|
});
|
|
640
1095
|
async function run() {
|
|
641
1096
|
// Start receiving messages on stdin and sending messages on stdout
|