// Agent Registry - Manages all AI agents and their capabilities
import OpenAI from 'openai';
import { Anthropic } from '@anthropic-ai/sdk';
class AgentRegistry {
constructor() {
this.agents = new Map();
this.capabilities = new Map();
this.resources = new Map();
this.initialized = false;
}
async initialize() {
if (this.initialized) return;
// Check required environment variables
if (!process.env.OPENAI_API_KEY) {
console.warn('⚠️ OPENAI_API_KEY not found - OpenAI agents will be unavailable');
}
if (!process.env.ANTHROPIC_API_KEY) {
console.warn('⚠️ ANTHROPIC_API_KEY not found - Claude agents will be unavailable');
}
// Initialize AI clients if keys are available
if (process.env.OPENAI_API_KEY) {
this.openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY
});
}
if (process.env.ANTHROPIC_API_KEY) {
this.anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY
});
}
// Load agents from database with graceful fallback
try {
await this.loadAgentsFromDatabase();
} catch (error) {
console.warn('⚠️ Database unavailable, using minimal agent configuration:', error.message);
await this.loadMinimalAgents();
}
this.initialized = true;
console.log('🤖 Agent Registry initialized with', this.agents.size, 'agents');
}
async loadMinimalAgents() {
// Minimal fallback agent configuration when database is unavailable
const minimalAgents = [
{ name: 'orchestrator', provider: 'openai', role: 'Orchestrator' },
{ name: 'creative_director', provider: 'anthropic', role: 'Creative Director' },
{ name: 'automation_engineer', provider: 'openai', role: 'Automation Engineer' },
{ name: 'docs_clerk', provider: 'system', role: 'Documentation Clerk' }
];
for (const agentConfig of minimalAgents) {
if ((agentConfig.provider === 'openai' && this.openai) ||
(agentConfig.provider === 'anthropic' && this.anthropic) ||
agentConfig.provider === 'system') {
const agent = {
id: Date.now() + Math.random(),
name: agentConfig.name,
provider: agentConfig.provider,
role: agentConfig.role,
config: {},
capabilities: [],
client: this.createAgentClient(agentConfig.provider, {})
};
this.agents.set(agentConfig.name, agent);
this.resources.set(agentConfig.name, {
dailyQuota: 100, // Reduced for fallback mode
usedUnits: 0,
windowStart: new Date(),
costBudget: 10.0,
costUsed: 0.0
});
}
}
}
async loadAgentsFromDatabase() {
try {
const { Pool } = await import('pg');
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
// Load agents and their capabilities
const agentsQuery = `
SELECT a.*,
json_agg(
json_build_object(
'tool_name', ac.tool_name,
'throughput', ac.throughput,
'cost_rate', ac.cost_rate,
'enabled', ac.enabled
)
) as capabilities
FROM agents a
LEFT JOIN agent_capabilities ac ON a.id = ac.agent_id
WHERE a.status = 'active'
GROUP BY a.id
`;
const result = await pool.query(agentsQuery);
for (const row of result.rows) {
const agent = {
id: row.id,
name: row.name,
provider: row.provider,
role: row.role,
config: row.config || {},
capabilities: row.capabilities.filter(cap => cap.tool_name) || [],
client: this.createAgentClient(row.provider, row.config)
};
this.agents.set(row.name, agent);
// Initialize resource tracking
this.resources.set(row.name, {
dailyQuota: 1000,
usedUnits: 0,
windowStart: new Date(),
costBudget: 100.0,
costUsed: 0.0
});
}
await pool.end();
} catch (error) {
console.error('❌ Failed to load agents from database:', error);
throw error;
}
}
createAgentClient(provider, config) {
switch (provider) {
case 'openai':
// Fallback model strategy: gpt-5 -> gpt-4o -> gpt-4
const openaiModel = config.model || process.env.OPENAI_MODEL || 'gpt-5';
return {
provider: 'openai',
client: this.openai,
model: openaiModel,
fallbacks: ['gpt-4o', 'gpt-4', 'gpt-3.5-turbo'],
temperature: config.temperature || 0.2
};
case 'anthropic':
const anthropicModel = config.model || process.env.ANTHROPIC_MODEL || 'claude-3-5-sonnet-20241022';
return {
provider: 'anthropic',
client: this.anthropic,
model: anthropicModel,
fallbacks: ['claude-3-haiku-20240307'],
temperature: config.temperature || 0.7
};
case 'system':
return {
provider: 'system',
client: null
};
default:
throw new Error(`Unknown provider: ${provider}`);
}
}
getAgent(name) {
const agent = this.agents.get(name);
if (!agent) {
throw new Error(`Agent '${name}' not found`);
}
return agent;
}
async callAgent(agentName, prompt, options = {}) {
const agent = this.getAgent(agentName);
// Check resource limits
if (!this.checkResourceLimit(agentName)) {
throw new Error(`Agent '${agentName}' has exceeded resource limits`);
}
try {
let response;
if (agent.client.provider === 'openai') {
const messages = [{ role: 'user', content: prompt }];
if (options.systemPrompt) {
messages.unshift({ role: 'system', content: options.systemPrompt });
}
const completion = await agent.client.client.chat.completions.create({
model: agent.client.model, // GPT-5 is the newest OpenAI model released August 7, 2025
messages: messages,
response_format: options.jsonMode ? { type: "json_object" } : undefined,
max_tokens: options.maxTokens || 4096
// Note: GPT-5 doesn't support temperature parameter
});
response = completion.choices[0].message.content;
} else if (agent.client.provider === 'anthropic') {
const messages = [{ role: 'user', content: prompt }];
const completion = await agent.client.client.messages.create({
model: agent.client.model,
messages: messages,
system: options.systemPrompt,
max_tokens: options.maxTokens || 4096,
temperature: agent.client.temperature
});
response = completion.content[0].text;
} else {
throw new Error(`System agents cannot be called directly`);
}
// Track resource usage
this.trackResourceUsage(agentName, 1, 0.01); // 1 request, estimated cost
return {
success: true,
response: response,
agent: agentName,
provider: agent.client.provider,
timestamp: new Date().toISOString()
};
} catch (error) {
console.error(`❌ Agent '${agentName}' call failed:`, error);
return {
success: false,
error: error.message,
agent: agentName,
timestamp: new Date().toISOString()
};
}
}
checkResourceLimit(agentName) {
const resource = this.resources.get(agentName);
if (!resource) return true;
// Reset daily quota if new day
const now = new Date();
if (now.getDate() !== resource.windowStart.getDate()) {
resource.usedUnits = 0;
resource.costUsed = 0.0;
resource.windowStart = now;
}
return resource.usedUnits < resource.dailyQuota &&
resource.costUsed < resource.costBudget;
}
trackResourceUsage(agentName, units, cost) {
const resource = this.resources.get(agentName);
if (resource) {
resource.usedUnits += units;
resource.costUsed += cost;
}
}
getAgentsByRole(role) {
return Array.from(this.agents.values()).filter(agent => agent.role === role);
}
getAgentCapabilities(agentName) {
const agent = this.getAgent(agentName);
return agent.capabilities || [];
}
async assignAgent(agentName, tool) {
const agent = this.getAgent(agentName);
const capabilities = agent.capabilities;
const canHandle = capabilities.some(cap =>
cap.tool_name === tool && cap.enabled
);
if (!canHandle) {
throw new Error(`Agent '${agentName}' cannot handle tool '${tool}'`);
}
return agent;
}
getResourceUsage(agentName) {
return this.resources.get(agentName) || null;
}
getAllAgents() {
return Array.from(this.agents.values());
}
}
// Singleton instance
export const agentRegistry = new AgentRegistry();