/**
* AI Orchestration Engine
* Chains multiple AI calls together, passing outputs as inputs
*/
import { getProvider } from './providers/index.js';
import { ChainConfig, ChainResult, AIResponse, Message } from './types.js';
import { Logger } from './logger.js';
export class AIOrchestrator {
private logger: Logger;
constructor(enableLogging: boolean = true) {
this.logger = new Logger(enableLogging);
}
/**
* Execute a chain of AI calls
* Each step's output becomes the next step's input
*/
async executeChain(config: ChainConfig): Promise<ChainResult> {
this.logger.log(`Starting chain: ${config.name}`);
const startTime = Date.now();
const result: ChainResult = {
chainName: config.name,
steps: [],
totalDuration: 0,
success: false
};
try {
let currentInput = config.initialInput;
const previousOutputs: AIResponse[] = [];
for (let i = 0; i < config.steps.length; i++) {
const step = config.steps[i];
this.logger.log(`\n${'='.repeat(60)}`);
this.logger.log(`Step ${i + 1}/${config.steps.length}: ${step.name}`);
this.logger.log(`Provider: ${step.provider} | Model: ${step.model}`);
this.logger.log(`${'='.repeat(60)}`);
const stepStartTime = Date.now();
// Apply transform function if provided
if (step.transform) {
currentInput = step.transform(currentInput, previousOutputs);
this.logger.log(`\nTransformed input applied`);
}
this.logger.log(`\nInput:\n${this.truncate(currentInput, 500)}`);
// Get provider and execute
const provider = getProvider(step.provider);
const messages: Message[] = [];
// Add system prompt if provided
if (step.systemPrompt) {
messages.push({
role: 'system',
content: step.systemPrompt
});
}
// Add user message
messages.push({
role: 'user',
content: currentInput
});
// Execute AI call
const output = await provider.call(messages, {
provider: step.provider,
model: step.model,
temperature: step.temperature,
maxTokens: step.maxTokens,
systemPrompt: step.systemPrompt
});
const stepDuration = Date.now() - stepStartTime;
this.logger.log(`\nOutput:\n${this.truncate(output.content, 500)}`);
this.logger.log(`\nDuration: ${stepDuration}ms`);
if (output.usage) {
this.logger.log(`Tokens: ${output.usage.totalTokens} (prompt: ${output.usage.promptTokens}, completion: ${output.usage.completionTokens})`);
}
// Store result
result.steps.push({
stepName: step.name,
input: currentInput,
output,
duration: stepDuration
});
previousOutputs.push(output);
// Next input is current output
currentInput = output.content;
}
result.success = true;
} catch (error) {
result.success = false;
result.error = error instanceof Error ? error.message : String(error);
this.logger.error(`\nChain failed: ${result.error}`);
}
result.totalDuration = Date.now() - startTime;
this.logger.log(`\n${'='.repeat(60)}`);
this.logger.log(`Chain completed: ${config.name}`);
this.logger.log(`Total duration: ${result.totalDuration}ms`);
this.logger.log(`Success: ${result.success}`);
this.logger.log(`${'='.repeat(60)}\n`);
return result;
}
/**
* Execute multiple chains in parallel
*/
async executeParallel(configs: ChainConfig[]): Promise<ChainResult[]> {
this.logger.log(`Executing ${configs.length} chains in parallel`);
return Promise.all(configs.map(config => this.executeChain(config)));
}
/**
* Get final output from chain result
*/
getFinalOutput(result: ChainResult): string | null {
if (!result.success || result.steps.length === 0) {
return null;
}
return result.steps[result.steps.length - 1].output.content;
}
/**
* Get output from specific step
*/
getStepOutput(result: ChainResult, stepName: string): string | null {
const step = result.steps.find(s => s.stepName === stepName);
return step ? step.output.content : null;
}
private truncate(text: string, maxLength: number): string {
if (text.length <= maxLength) return text;
return text.substring(0, maxLength) + '... (truncated)';
}
}