UNPKG

@dondonudonjp/vertexai-imagen-mcp-server

Version:

[DEPRECATED] MCP Server for Vertex AI Imagen image generation. Imagen endpoints shut down 2026-06-30; migrate to the nanoBanana MCP Server (Gemini image models).

239 lines 11.2 kB
/** * バッチ画像生成処理 */ import { readFile } from 'fs/promises'; import { batchJobItemToGenerateImageArgs } from './types/batch.js'; import { getDefaultOutputDirectory } from './utils/path.js'; /** * バッチ処理実行クラス */ export class BatchProcessor { jobManager; jobDatabase; outputDir; constructor(jobManager, jobDatabase, outputDir) { this.jobManager = jobManager; this.jobDatabase = jobDatabase; this.outputDir = outputDir || getDefaultOutputDirectory(); } /** * JSONファイルからバッチ設定を読み込む */ async loadBatchConfig(filePath) { const content = await readFile(filePath, 'utf-8'); const config = JSON.parse(content); // バリデーション if (!config.jobs || !Array.isArray(config.jobs) || config.jobs.length === 0) { throw new Error('Invalid batch config: "jobs" array is required and must not be empty'); } for (let i = 0; i < config.jobs.length; i++) { const job = config.jobs[i]; if (!job.prompt || typeof job.prompt !== 'string') { throw new Error(`Invalid batch config: jobs[${i}].prompt is required and must be a string`); } } return config; } /** * バッチ処理を実行 */ async executeBatch(config) { const startTime = Date.now(); const jobIds = []; const outputDir = config.output_dir || this.outputDir; console.error(`[BATCH] Starting batch processing with ${config.jobs.length} jobs`); console.error(`[BATCH] Output directory: ${outputDir}`); // 全てのジョブを投入 for (let i = 0; i < config.jobs.length; i++) { const item = config.jobs[i]; const args = batchJobItemToGenerateImageArgs(item, outputDir); console.error(`[BATCH] Queueing job ${i + 1}/${config.jobs.length}: ${item.prompt.substring(0, 50)}...`); try { const jobId = this.jobManager.createJob('generate', args); jobIds.push(jobId); if (process.env.DEBUG) { console.error(`[DEBUG] Job created with ID: ${jobId}`); } } catch (error) { console.error(`[BATCH] Failed to create job ${i + 1}:`, error); // エラーが発生してもジョブIDに空文字を追加して、インデックスを保持 jobIds.push(''); } } console.error(`[BATCH] All jobs queued. Waiting for completion...`); // ジョブの完了を待つ const timeout = config.timeout || 600000; // デフォルト10分 const results = await this.waitForJobsCompletion(jobIds, config.jobs, timeout); const endTime = Date.now(); const result = { total: config.jobs.length, succeeded: results.filter(r => r.status === 'completed').length, failed: results.filter(r => r.status === 'failed').length, results, started_at: new Date(startTime).toISOString(), finished_at: new Date(endTime).toISOString(), total_duration_ms: endTime - startTime, }; console.error(`[BATCH] Batch processing completed:`); console.error(`[BATCH] Total: ${result.total}`); console.error(`[BATCH] Succeeded: ${result.succeeded}`); console.error(`[BATCH] Failed: ${result.failed}`); console.error(`[BATCH] Duration: ${result.total_duration_ms}ms`); return result; } /** * 全てのジョブが完了するまで待機 */ async waitForJobsCompletion(jobIds, items, timeout) { const results = []; const startTime = Date.now(); const checkInterval = 2000; // 2秒ごとにチェック // 初期化:全てのジョブを結果配列に追加 for (let i = 0; i < jobIds.length; i++) { results.push({ job_id: jobIds[i] || 'N/A', prompt: items[i].prompt, // ジョブ作成に失敗した場合のみ failed、それ以外は pending で初期化 status: jobIds[i] ? 'pending' : 'failed', error: jobIds[i] ? undefined : 'Failed to create job', }); } if (process.env.DEBUG) { console.error(`[DEBUG] Waiting for ${jobIds.length} jobs to complete`); for (let i = 0; i < jobIds.length; i++) { const job = jobIds[i] ? this.jobDatabase.getJob(jobIds[i]) : null; console.error(`[DEBUG] Job ${i + 1}: ID=${jobIds[i]}, initial DB status=${job?.status || 'not found'}`); } } while (true) { // タイムアウトチェック if (Date.now() - startTime > timeout) { console.error('[BATCH] Timeout reached. Cancelling remaining jobs...'); for (let i = 0; i < jobIds.length; i++) { const jobId = jobIds[i]; if (!jobId) continue; const result = results[i]; if (result.status !== 'completed' && result.status !== 'failed') { this.jobManager.cancelJob(jobId); result.status = 'cancelled'; result.error = 'Timeout'; } } break; } // 全てのジョブのステータスをチェック let allCompleted = true; if (process.env.DEBUG) { console.error(`[DEBUG] Checking job statuses (elapsed: ${Date.now() - startTime}ms)`); } for (let i = 0; i < jobIds.length; i++) { const jobId = jobIds[i]; if (!jobId) continue; // スキップ(ジョブ作成失敗) const result = results[i]; if (result.status === 'completed' || result.status === 'failed' || result.status === 'cancelled') { continue; // 既に完了 } const job = this.jobDatabase.getJob(jobId); if (!job) { result.status = 'failed'; result.error = 'Job not found'; if (process.env.DEBUG) { console.error(`[DEBUG] Job ${jobId} not found in database`); } continue; } if (process.env.DEBUG && (job.status === 'failed' || job.status === 'completed')) { console.error(`[DEBUG] Job ${jobId} status: ${job.status}, error: "${job.error || 'none'}"`); } if (job.status === 'completed') { result.status = 'completed'; // GenerateJobのresultはoutputPaths配列を持つので、最初の要素を取得 result.output_path = job.result && 'outputPaths' in job.result ? job.result.outputPaths[0] : undefined; result.duration_ms = job.completedAt ? job.completedAt.getTime() - job.createdAt.getTime() : undefined; if (process.env.DEBUG) { console.error(`[DEBUG] Job ${jobId} completed successfully. Output: ${result.output_path}`); } } else if (job.status === 'failed') { result.status = 'failed'; // エラーメッセージを取得(空文字列や未定義の場合はデフォルトメッセージ) const errorMsg = job.error && job.error.trim() ? job.error : 'Job failed with no error message'; result.error = errorMsg; result.duration_ms = job.completedAt ? job.completedAt.getTime() - job.createdAt.getTime() : undefined; // デバッグ: エラー内容を確認 if (process.env.DEBUG) { console.error(`[DEBUG] Job ${jobId} failed. DB error field: "${job.error}", Using error: "${result.error}"`); if (job.completedAt && job.createdAt) { console.error(`[DEBUG] Duration calc: completedAt=${job.completedAt.toISOString()}, createdAt=${job.createdAt.toISOString()}, duration=${result.duration_ms}ms`); } } } else { allCompleted = false; // まだ実行中のジョブがある } } if (allCompleted) { break; // 全てのジョブが完了 } // 進行状況を出力 const completed = results.filter(r => r.status === 'completed').length; const failed = results.filter(r => r.status === 'failed' || r.status === 'cancelled').length; const pending = results.length - completed - failed; console.error(`[BATCH] Progress: ${completed} completed, ${failed} failed, ${pending} pending`); // 待機 await new Promise(resolve => setTimeout(resolve, checkInterval)); } return results; } /** * バッチ結果をJSON形式で出力 */ formatResultAsJson(result) { return JSON.stringify(result, null, 2); } /** * バッチ結果を人間が読める形式で出力 */ formatResultAsText(result) { const lines = []; lines.push('=== Batch Processing Result ==='); lines.push(`Total Jobs: ${result.total}`); lines.push(`Succeeded: ${result.succeeded}`); lines.push(`Failed: ${result.failed}`); lines.push(`Duration: ${result.total_duration_ms}ms`); lines.push(`Started: ${result.started_at}`); lines.push(`Finished: ${result.finished_at}`); lines.push(''); lines.push('=== Individual Results ==='); for (let i = 0; i < result.results.length; i++) { const r = result.results[i]; lines.push(`\n[${i + 1}/${result.results.length}] ${r.status.toUpperCase()}`); lines.push(` Job ID: ${r.job_id}`); lines.push(` Prompt: ${r.prompt}`); if (r.output_path) { lines.push(` Output: ${r.output_path}`); } if (r.error && r.error.trim()) { lines.push(` Error: ${r.error}`); } else if (r.status === 'failed' || r.status === 'cancelled') { // ステータスがfailed/cancelledなのにエラーメッセージがない場合 lines.push(` Error: No error message recorded`); } if (r.duration_ms !== undefined) { lines.push(` Duration: ${r.duration_ms}ms`); } } return lines.join('\n'); } } //# sourceMappingURL=batch.js.map