claude-gemini-multimodal-bridge
Version:
Enterprise-grade AI integration bridge connecting Claude Code, Gemini CLI, and Google AI Studio with intelligent routing and advanced multimodal processing capabilities
619 lines (618 loc) • 24.4 kB
JavaScript
import { WorkflowOrchestrator } from '../tools/workflowOrchestrator.js';
import { MultimodalProcess } from '../tools/multimodalProcess.js';
import { logger } from '../utils/logger.js';
import { safeExecute } from '../utils/errorHandler.js';
import path from 'path';
export class ConversionWorkflow {
id;
steps;
continueOnError;
timeout;
orchestrator;
multimodalProcess;
SUPPORTED_CONVERSIONS = {
document: {
from: ['.pdf', '.doc', '.docx', '.txt', '.md', '.rtf', '.odt'],
to: ['.pdf', '.docx', '.txt', '.md', '.html'],
},
image: {
from: ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp', '.tiff'],
to: ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp', '.svg'],
},
audio: {
from: ['.mp3', '.wav', '.m4a', '.flac', '.aac', '.ogg'],
to: ['.mp3', '.wav', '.m4a', '.flac'],
},
data: {
from: ['.csv', '.xlsx', '.json', '.xml', '.yaml'],
to: ['.csv', '.xlsx', '.json', '.xml', '.yaml'],
},
presentation: {
from: ['.ppt', '.pptx', '.odp'],
to: ['.pptx', '.pdf', '.html'],
},
};
constructor(id) {
this.id = id || `conversion_workflow_${Date.now()}`;
this.steps = [];
this.continueOnError = false;
this.timeout = 600000;
this.orchestrator = new WorkflowOrchestrator();
this.multimodalProcess = new MultimodalProcess();
}
async executeDocumentConversion(files, targetFormat, options) {
return safeExecute(async () => {
logger.info('Starting document conversion workflow', {
fileCount: files.length,
targetFormat,
workflowId: this.id,
});
this.validateDocumentConversion(files, targetFormat);
const workflow = this.createDocumentConversionWorkflow(files, targetFormat, options);
return await this.orchestrator.executeWorkflow(workflow);
}, {
operationName: 'document-conversion-workflow',
layer: 'claude',
timeout: this.timeout,
});
}
async executeImageConversion(files, targetFormat, options) {
return safeExecute(async () => {
logger.info('Starting image conversion workflow', {
fileCount: files.length,
targetFormat,
workflowId: this.id,
});
this.validateImageConversion(files, targetFormat);
const workflow = this.createImageConversionWorkflow(files, targetFormat, options);
return await this.orchestrator.executeWorkflow(workflow);
}, {
operationName: 'image-conversion-workflow',
layer: 'claude',
timeout: this.timeout,
});
}
async executeAudioConversion(files, targetFormat, options) {
return safeExecute(async () => {
logger.info('Starting audio conversion workflow', {
fileCount: files.length,
targetFormat,
workflowId: this.id,
});
this.validateAudioConversion(files, targetFormat);
const workflow = this.createAudioConversionWorkflow(files, targetFormat, options);
return await this.orchestrator.executeWorkflow(workflow);
}, {
operationName: 'audio-conversion-workflow',
layer: 'claude',
timeout: this.timeout,
});
}
async executeDataConversion(files, targetFormat, options) {
return safeExecute(async () => {
logger.info('Starting data conversion workflow', {
fileCount: files.length,
targetFormat,
workflowId: this.id,
});
this.validateDataConversion(files, targetFormat);
const workflow = this.createDataConversionWorkflow(files, targetFormat, options);
return await this.orchestrator.executeWorkflow(workflow);
}, {
operationName: 'data-conversion-workflow',
layer: 'claude',
timeout: this.timeout,
});
}
async executeBatchConversion(conversions) {
return safeExecute(async () => {
logger.info('Starting batch conversion workflow', {
conversionCount: conversions.length,
workflowId: this.id,
});
const workflow = this.createBatchConversionWorkflow(conversions);
return await this.orchestrator.executeWorkflow(workflow);
}, {
operationName: 'batch-conversion-workflow',
layer: 'claude',
timeout: this.timeout * conversions.length,
});
}
async executeContentExtractionConversion(files, extractionType, targetFormat, options) {
return safeExecute(async () => {
logger.info('Starting content extraction conversion workflow', {
fileCount: files.length,
extractionType,
targetFormat,
workflowId: this.id,
});
const workflow = this.createContentExtractionConversionWorkflow(files, extractionType, targetFormat, options);
return await this.orchestrator.executeWorkflow(workflow);
}, {
operationName: 'content-extraction-conversion-workflow',
layer: 'claude',
timeout: this.timeout,
});
}
async createExecutionPlan(files, prompt, options) {
const conversionComplexity = this.assessConversionComplexity(files, options);
const totalSize = files.reduce((sum, file) => sum + (file.size || 0), 0);
const phases = [];
let estimatedDuration = 0;
let estimatedCost = 0;
phases.push({
name: 'preprocessing',
steps: ['validate_files', 'analyze_formats', 'prepare_conversion'],
requiredLayers: ['aistudio'],
});
estimatedDuration += 30000;
const conversionDuration = this.estimateConversionDuration(files, conversionComplexity);
phases.push({
name: 'conversion',
steps: ['execute_conversion', 'verify_output'],
requiredLayers: ['aistudio'],
});
estimatedDuration += conversionDuration;
estimatedCost += this.estimateConversionCost(files, conversionComplexity);
phases.push({
name: 'postprocessing',
steps: ['quality_check', 'optimize_output', 'generate_metadata'],
requiredLayers: ['claude'],
});
estimatedDuration += 60000;
return {
steps: [],
timeout: estimatedDuration,
};
}
validateInputs(inputs) {
if (!inputs.files || !Array.isArray(inputs.files) || inputs.files.length === 0) {
return false;
}
if (!inputs.targetFormat || typeof inputs.targetFormat !== 'string') {
return false;
}
for (const file of inputs.files) {
if (!file.path || typeof file.path !== 'string') {
return false;
}
const ext = path.extname(file.path).toLowerCase();
if (!this.isSupportedSourceFormat(ext)) {
return false;
}
}
return true;
}
estimateResourceRequirements(inputs) {
const fileCount = inputs.files?.length || 0;
const totalSize = inputs.files?.reduce((sum, file) => sum + (file.size || 0), 0) || 0;
const complexity = this.assessConversionComplexity(inputs.files, inputs.options);
const memory = 1024;
const cpu = 1.5;
const duration = 120000;
const cost = 0.02;
const sizeMultiplier = Math.min(totalSize / (10 * 1024 * 1024), 10);
const countMultiplier = Math.min(fileCount * 0.5, 5);
const complexityMultipliers = {
low: { estimated_tokens: 1, complexity_score: 1, estimated_duration: 1, estimated_cost: 1 },
medium: { estimated_tokens: 1.5, complexity_score: 1.3, estimated_duration: 1.5, estimated_cost: 1.3 },
high: { estimated_tokens: 2.5, complexity_score: 2, estimated_duration: 2.5, estimated_cost: 2 },
};
const multiplier = complexityMultipliers[complexity];
return {
estimated_tokens: memory * Math.max(sizeMultiplier, countMultiplier) * multiplier.estimated_tokens,
complexity_score: cpu * multiplier.complexity_score,
estimated_duration: duration * Math.max(sizeMultiplier, countMultiplier) * multiplier.estimated_duration,
recommended_execution_mode: 'adaptive',
required_capabilities: ['claude', 'gemini', 'aistudio'],
estimated_cost: cost * multiplier.estimated_cost,
};
}
validateDocumentConversion(files, targetFormat) {
const supportedSource = this.SUPPORTED_CONVERSIONS.document.from;
const supportedTarget = this.SUPPORTED_CONVERSIONS.document.to;
if (!targetFormat.startsWith('.')) {
targetFormat = '.' + targetFormat;
}
if (!supportedTarget.includes(targetFormat)) {
throw new Error(`Unsupported target format for documents: ${targetFormat}`);
}
for (const file of files) {
const ext = path.extname(file.path).toLowerCase();
if (!supportedSource.includes(ext)) {
throw new Error(`Unsupported source format for document: ${file.path}`);
}
}
}
validateImageConversion(files, targetFormat) {
const supportedSource = this.SUPPORTED_CONVERSIONS.image.from;
const supportedTarget = this.SUPPORTED_CONVERSIONS.image.to;
if (!targetFormat.startsWith('.')) {
targetFormat = '.' + targetFormat;
}
if (!supportedTarget.includes(targetFormat)) {
throw new Error(`Unsupported target format for images: ${targetFormat}`);
}
for (const file of files) {
const ext = path.extname(file.path).toLowerCase();
if (!supportedSource.includes(ext)) {
throw new Error(`Unsupported source format for image: ${file.path}`);
}
}
}
validateAudioConversion(files, targetFormat) {
const supportedSource = this.SUPPORTED_CONVERSIONS.audio.from;
const supportedTarget = this.SUPPORTED_CONVERSIONS.audio.to;
if (!targetFormat.startsWith('.')) {
targetFormat = '.' + targetFormat;
}
if (!supportedTarget.includes(targetFormat)) {
throw new Error(`Unsupported target format for audio: ${targetFormat}`);
}
for (const file of files) {
const ext = path.extname(file.path).toLowerCase();
if (!supportedSource.includes(ext)) {
throw new Error(`Unsupported source format for audio: ${file.path}`);
}
}
}
validateDataConversion(files, targetFormat) {
const supportedSource = this.SUPPORTED_CONVERSIONS.data.from;
const supportedTarget = this.SUPPORTED_CONVERSIONS.data.to;
if (!targetFormat.startsWith('.')) {
targetFormat = '.' + targetFormat;
}
if (!supportedTarget.includes(targetFormat)) {
throw new Error(`Unsupported target format for data: ${targetFormat}`);
}
for (const file of files) {
const ext = path.extname(file.path).toLowerCase();
if (!supportedSource.includes(ext)) {
throw new Error(`Unsupported source format for data: ${file.path}`);
}
}
}
createDocumentConversionWorkflow(files, targetFormat, options) {
return {
id: `document_conversion_${Date.now()}`,
steps: [
{
id: 'prepare_documents',
layer: 'aistudio',
action: 'document_analysis',
input: {
files,
instructions: 'Analyze document structure and content for conversion',
},
dependsOn: [],
},
{
id: 'convert_documents',
layer: 'aistudio',
action: 'convert_file',
input: {
files,
outputFormat: targetFormat,
options: {
preserveFormatting: options?.preserveFormatting ?? true,
extractImages: options?.extractImages ?? false,
quality: options?.quality ?? 'high',
},
},
dependsOn: ['prepare_documents'],
},
{
id: 'verify_conversion',
layer: 'claude',
action: 'synthesize_response',
input: {
request: 'Verify conversion quality and generate conversion report',
inputs: {
originalAnalysis: '{{prepare_documents}}',
conversionResult: '{{convert_documents}}',
},
},
dependsOn: ['convert_documents'],
},
],
continueOnError: false,
timeout: this.timeout,
};
}
createImageConversionWorkflow(files, targetFormat, options) {
return {
id: `image_conversion_${Date.now()}`,
steps: [
{
id: 'analyze_images',
layer: 'aistudio',
action: 'analyze_image',
input: {
files,
instructions: 'Analyze image properties for optimal conversion',
},
dependsOn: [],
},
{
id: 'convert_images',
layer: 'aistudio',
action: 'convert_file',
input: {
files,
outputFormat: targetFormat,
options: {
resize: options?.resize,
quality: options?.quality ?? 90,
compress: options?.compress ?? false,
},
},
dependsOn: ['analyze_images'],
},
{
id: 'verify_image_quality',
layer: 'claude',
action: 'synthesize_response',
input: {
request: 'Assess image conversion quality and provide optimization recommendations',
inputs: {
originalAnalysis: '{{analyze_images}}',
conversionResult: '{{convert_images}}',
},
},
dependsOn: ['convert_images'],
},
],
continueOnError: true,
timeout: this.timeout,
};
}
createAudioConversionWorkflow(files, targetFormat, options) {
return {
id: `audio_conversion_${Date.now()}`,
steps: [
{
id: 'analyze_audio',
layer: 'aistudio',
action: 'transcribe_audio',
input: {
files,
instructions: 'Analyze audio properties and quality',
},
dependsOn: [],
},
{
id: 'convert_audio',
layer: 'aistudio',
action: 'convert_file',
input: {
files,
outputFormat: targetFormat,
options: {
bitrate: options?.bitrate,
sampleRate: options?.sampleRate,
channels: options?.channels,
},
},
dependsOn: ['analyze_audio'],
},
{
id: 'verify_audio_quality',
layer: 'claude',
action: 'synthesize_response',
input: {
request: 'Verify audio conversion quality and generate quality report',
inputs: {
originalAnalysis: '{{analyze_audio}}',
conversionResult: '{{convert_audio}}',
},
},
dependsOn: ['convert_audio'],
},
],
continueOnError: true,
timeout: this.timeout,
};
}
createDataConversionWorkflow(files, targetFormat, options) {
return {
id: `data_conversion_${Date.now()}`,
steps: [
{
id: 'analyze_data_structure',
layer: 'aistudio',
action: 'document_analysis',
input: {
files,
instructions: 'Analyze data structure and schema for conversion',
},
dependsOn: [],
},
{
id: 'convert_data_format',
layer: 'aistudio',
action: 'convert_file',
input: {
files,
outputFormat: targetFormat,
options: {
preserveStructure: options?.preserveStructure ?? true,
includeMetadata: options?.includeMetadata ?? true,
encoding: options?.encoding ?? 'utf-8',
},
},
dependsOn: ['analyze_data_structure'],
},
{
id: 'validate_data_integrity',
layer: 'claude',
action: 'complex_reasoning',
input: {
prompt: 'Validate data integrity and structure preservation after conversion',
context: 'Original: {{analyze_data_structure}}, Converted: {{convert_data_format}}',
depth: 'medium',
},
dependsOn: ['convert_data_format'],
},
],
continueOnError: false,
timeout: this.timeout,
};
}
createBatchConversionWorkflow(conversions) {
const steps = [];
conversions.forEach((conversion, index) => {
steps.push({
id: `batch_conversion_${index}`,
layer: 'aistudio',
action: 'convert_file',
input: {
files: conversion.files,
outputFormat: conversion.targetFormat,
options: conversion.options || {},
},
dependsOn: [],
});
});
steps.push({
id: 'summarize_batch_conversion',
layer: 'claude',
action: 'synthesize_response',
input: {
request: 'Summarize batch conversion results and provide quality assessment',
inputs: Object.fromEntries(conversions.map((_, index) => [`conversion_${index}`, `{{batch_conversion_${index}}}`])),
},
dependsOn: conversions.map((_, index) => `batch_conversion_${index}`),
});
return {
id: `batch_conversion_${Date.now()}`,
steps,
continueOnError: true,
timeout: this.timeout * conversions.length,
};
}
createContentExtractionConversionWorkflow(files, extractionType, targetFormat, options) {
return {
id: `content_extraction_conversion_${Date.now()}`,
steps: [
{
id: 'extract_content',
layer: 'aistudio',
action: 'multimodal_processing',
input: {
files,
instructions: `Extract ${extractionType} content from files`,
options: { extractionType },
},
dependsOn: [],
},
{
id: 'convert_extracted_content',
layer: 'aistudio',
action: 'convert_file',
input: {
content: '{{extract_content}}',
outputFormat: targetFormat,
options: options || {},
},
dependsOn: ['extract_content'],
},
{
id: 'organize_converted_content',
layer: 'claude',
action: 'synthesize_response',
input: {
request: 'Organize and structure the converted content with metadata',
inputs: {
extractedContent: '{{extract_content}}',
convertedContent: '{{convert_extracted_content}}',
},
},
dependsOn: ['convert_extracted_content'],
},
],
continueOnError: false,
timeout: this.timeout,
};
}
assessConversionComplexity(files, options) {
let complexityScore = 0;
if (files.length > 20) {
complexityScore += 3;
}
else if (files.length > 10) {
complexityScore += 2;
}
else if (files.length > 5) {
complexityScore += 1;
}
const totalSize = files.reduce((sum, file) => sum + (file.size || 0), 0);
if (totalSize > 500 * 1024 * 1024) {
complexityScore += 3;
}
else if (totalSize > 100 * 1024 * 1024) {
complexityScore += 2;
}
else if (totalSize > 10 * 1024 * 1024) {
complexityScore += 1;
}
if (options?.quality === 'high') {
complexityScore += 1;
}
if (options?.preserveFormatting) {
complexityScore += 1;
}
if (options?.extractImages) {
complexityScore += 1;
}
if (options?.resize) {
complexityScore += 1;
}
if (complexityScore >= 6) {
return 'high';
}
if (complexityScore >= 3) {
return 'medium';
}
return 'low';
}
estimateConversionDuration(files, complexity) {
const baseTime = 30000;
const fileMultiplier = Math.min(files.length * 0.5, 10);
const complexityMultipliers = {
low: 1,
medium: 2,
high: 4,
};
return baseTime * fileMultiplier * complexityMultipliers[complexity];
}
estimateConversionCost(files, complexity) {
const baseCost = 0.005;
const fileCount = files.length;
const complexityMultipliers = {
low: 1,
medium: 1.5,
high: 2.5,
};
return baseCost * fileCount * complexityMultipliers[complexity];
}
isSupportedSourceFormat(extension) {
return Object.values(this.SUPPORTED_CONVERSIONS)
.some(conv => conv.from.includes(extension));
}
getSupportedConversions() {
return this.SUPPORTED_CONVERSIONS;
}
getAvailableConversionTypes() {
return Object.keys(this.SUPPORTED_CONVERSIONS);
}
getCapabilities() {
return [
'document_conversion',
'image_conversion',
'audio_conversion',
'data_conversion',
'batch_conversion',
'content_extraction_conversion',
];
}
}