220 lines
8.0 KiB
JavaScript
220 lines
8.0 KiB
JavaScript
/**
|
|
* Long-Running Agent with Provider Fallback
|
|
*
|
|
* Demonstrates how to use ProviderManager for resilient, cost-optimized agents
|
|
* that can run for hours or days with automatic provider switching.
|
|
*/
|
|
import { ProviderManager } from './provider-manager.js';
|
|
import { logger } from '../utils/logger.js';
|
|
export class LongRunningAgent {
|
|
providerManager;
|
|
config;
|
|
startTime;
|
|
checkpoints = [];
|
|
currentState = {};
|
|
isRunning = false;
|
|
checkpointInterval;
|
|
constructor(config) {
|
|
this.config = config;
|
|
this.startTime = new Date();
|
|
// Initialize provider manager
|
|
this.providerManager = new ProviderManager(config.providers, config.fallbackStrategy);
|
|
logger.info('Long-running agent initialized', {
|
|
agentName: config.agentName,
|
|
providers: config.providers.map(p => p.name)
|
|
});
|
|
}
|
|
/**
|
|
* Start the agent with automatic checkpointing
|
|
*/
|
|
async start() {
|
|
this.isRunning = true;
|
|
this.startTime = new Date();
|
|
// Start checkpoint interval
|
|
if (this.config.checkpointInterval) {
|
|
this.checkpointInterval = setInterval(() => {
|
|
this.saveCheckpoint();
|
|
}, this.config.checkpointInterval);
|
|
}
|
|
logger.info('Long-running agent started', {
|
|
agentName: this.config.agentName,
|
|
startTime: this.startTime
|
|
});
|
|
}
|
|
/**
|
|
* Execute a task with automatic provider fallback
|
|
*/
|
|
async executeTask(task) {
|
|
if (!this.isRunning) {
|
|
throw new Error('Agent not running. Call start() first.');
|
|
}
|
|
// Check budget constraint
|
|
if (this.config.costBudget) {
|
|
const currentCost = this.providerManager.getCostSummary().total;
|
|
if (currentCost >= this.config.costBudget) {
|
|
throw new Error(`Cost budget exceeded: $${currentCost.toFixed(2)} >= $${this.config.costBudget}`);
|
|
}
|
|
}
|
|
// Check runtime constraint
|
|
if (this.config.maxRuntime) {
|
|
const runtime = Date.now() - this.startTime.getTime();
|
|
if (runtime >= this.config.maxRuntime) {
|
|
throw new Error(`Max runtime exceeded: ${runtime}ms >= ${this.config.maxRuntime}ms`);
|
|
}
|
|
}
|
|
logger.info('Executing task', {
|
|
agentName: this.config.agentName,
|
|
taskName: task.name,
|
|
complexity: task.complexity
|
|
});
|
|
try {
|
|
// Execute with automatic fallback
|
|
const { result, provider, attempts } = await this.providerManager.executeWithFallback(task.execute, task.complexity, task.estimatedTokens);
|
|
// Update state
|
|
this.currentState.lastTask = task.name;
|
|
this.currentState.lastProvider = provider;
|
|
this.currentState.completedTasks = (this.currentState.completedTasks || 0) + 1;
|
|
logger.info('Task completed', {
|
|
agentName: this.config.agentName,
|
|
taskName: task.name,
|
|
provider,
|
|
attempts
|
|
});
|
|
return result;
|
|
}
|
|
catch (error) {
|
|
this.currentState.failedTasks = (this.currentState.failedTasks || 0) + 1;
|
|
logger.error('Task failed', {
|
|
agentName: this.config.agentName,
|
|
taskName: task.name,
|
|
error: error.message
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
/**
|
|
* Save checkpoint of current state
|
|
*/
|
|
saveCheckpoint() {
|
|
const costSummary = this.providerManager.getCostSummary();
|
|
const health = this.providerManager.getHealth();
|
|
const checkpoint = {
|
|
timestamp: new Date(),
|
|
taskProgress: this.calculateProgress(),
|
|
currentProvider: this.currentState.lastProvider || 'none',
|
|
totalCost: costSummary.total,
|
|
totalTokens: costSummary.totalTokens,
|
|
completedTasks: this.currentState.completedTasks || 0,
|
|
failedTasks: this.currentState.failedTasks || 0,
|
|
state: { ...this.currentState }
|
|
};
|
|
this.checkpoints.push(checkpoint);
|
|
logger.info('Checkpoint saved', {
|
|
agentName: this.config.agentName,
|
|
checkpoint: {
|
|
...checkpoint,
|
|
state: undefined // Don't log full state
|
|
}
|
|
});
|
|
// Alert if cost approaching budget
|
|
if (this.config.costBudget) {
|
|
const costPercentage = (costSummary.total / this.config.costBudget) * 100;
|
|
if (costPercentage >= 80) {
|
|
logger.warn('Cost budget warning', {
|
|
agentName: this.config.agentName,
|
|
currentCost: costSummary.total,
|
|
budget: this.config.costBudget,
|
|
percentage: costPercentage.toFixed(1) + '%'
|
|
});
|
|
}
|
|
}
|
|
// Alert if providers unhealthy
|
|
const unhealthyProviders = health.filter(h => !h.isHealthy || h.circuitBreakerOpen);
|
|
if (unhealthyProviders.length > 0) {
|
|
logger.warn('Unhealthy providers detected', {
|
|
agentName: this.config.agentName,
|
|
unhealthy: unhealthyProviders.map(h => ({
|
|
provider: h.provider,
|
|
circuitBreakerOpen: h.circuitBreakerOpen,
|
|
consecutiveFailures: h.consecutiveFailures
|
|
}))
|
|
});
|
|
}
|
|
}
|
|
/**
|
|
* Calculate task progress (override in subclass)
|
|
*/
|
|
calculateProgress() {
|
|
// Default: based on completed vs total tasks
|
|
const completed = this.currentState.completedTasks || 0;
|
|
const failed = this.currentState.failedTasks || 0;
|
|
const total = completed + failed;
|
|
return total > 0 ? completed / total : 0;
|
|
}
|
|
/**
|
|
* Get current status
|
|
*/
|
|
getStatus() {
|
|
const costSummary = this.providerManager.getCostSummary();
|
|
const health = this.providerManager.getHealth();
|
|
const runtime = Date.now() - this.startTime.getTime();
|
|
return {
|
|
isRunning: this.isRunning,
|
|
runtime,
|
|
completedTasks: this.currentState.completedTasks || 0,
|
|
failedTasks: this.currentState.failedTasks || 0,
|
|
totalCost: costSummary.total,
|
|
totalTokens: costSummary.totalTokens,
|
|
providers: health.map(h => ({
|
|
name: h.provider,
|
|
healthy: h.isHealthy,
|
|
circuitBreakerOpen: h.circuitBreakerOpen,
|
|
successRate: (h.successRate * 100).toFixed(1) + '%',
|
|
avgLatency: h.averageLatency.toFixed(0) + 'ms'
|
|
})),
|
|
lastCheckpoint: this.checkpoints[this.checkpoints.length - 1]
|
|
};
|
|
}
|
|
/**
|
|
* Get detailed metrics
|
|
*/
|
|
getMetrics() {
|
|
return {
|
|
providers: this.providerManager.getMetrics(),
|
|
health: this.providerManager.getHealth(),
|
|
costs: this.providerManager.getCostSummary(),
|
|
checkpoints: this.checkpoints
|
|
};
|
|
}
|
|
/**
|
|
* Restore from checkpoint
|
|
*/
|
|
restoreFromCheckpoint(checkpoint) {
|
|
this.currentState = { ...checkpoint.state };
|
|
logger.info('Restored from checkpoint', {
|
|
agentName: this.config.agentName,
|
|
checkpoint: checkpoint.timestamp
|
|
});
|
|
}
|
|
/**
|
|
* Stop the agent
|
|
*/
|
|
async stop() {
|
|
this.isRunning = false;
|
|
// Clear checkpoint interval
|
|
if (this.checkpointInterval) {
|
|
clearInterval(this.checkpointInterval);
|
|
}
|
|
// Save final checkpoint
|
|
this.saveCheckpoint();
|
|
// Cleanup provider manager
|
|
this.providerManager.destroy();
|
|
logger.info('Long-running agent stopped', {
|
|
agentName: this.config.agentName,
|
|
runtime: Date.now() - this.startTime.getTime(),
|
|
completedTasks: this.currentState.completedTasks,
|
|
failedTasks: this.currentState.failedTasks
|
|
});
|
|
}
|
|
}
|
|
//# sourceMappingURL=long-running-agent.js.map
|