UNPKG

@tehreet/conduit

Version:

LLM API gateway with intelligent routing, robust process management, and health monitoring

333 lines 12 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SynapseIntegration = void 0; const events_1 = require("events"); const ConduitRouter_1 = require("../router/ConduitRouter"); const context_extractor_1 = require("../utils/context-extractor"); const UsageTracker_1 = require("../monitoring/UsageTracker"); const token_counter_1 = require("../utils/token-counter"); const log_1 = require("../utils/log"); /** * Synapse integration layer for Conduit */ class SynapseIntegration extends events_1.EventEmitter { constructor(config) { super(); this.config = config; this.router = new ConduitRouter_1.ConduitRouter(config); this.usageTracker = new UsageTracker_1.UsageTracker({ enabled: config.monitoring?.usage?.enabled ?? true, storage: config.monitoring?.usage?.storage ? { type: config.monitoring.usage.storage, retention: config.monitoring.usage.retention } : undefined, metrics: config.monitoring?.metrics ? { enabled: config.monitoring.metrics.enabled } : undefined }); this.contextExtractor = new context_extractor_1.ContextExtractor(); } /** * Initialize the Synapse integration */ async initialize() { (0, log_1.log)('Initializing Synapse integration...'); await this.router.initialize(); await this.usageTracker.initialize(); // Set up event forwarding this.setupEventForwarding(); this.emit('initialized'); (0, log_1.log)('Synapse integration initialized'); } /** * Route a Claude CLI request with Synapse context */ async routeClaudeRequest(args, env = process.env) { try { // Extract Synapse context from environment const synapseContext = this.contextExtractor.extractSynapseContext(env); // Parse Claude arguments const parsedArgs = this.parseClaudeArgs(args); // Estimate token count const tokenCount = await this.estimateTokenCount(parsedArgs, synapseContext); // Create routing context const routingContext = { request: { body: { model: parsedArgs.model, messages: parsedArgs.messages, thinking: parsedArgs.thinking, system: parsedArgs.systemPrompt } }, tokenCount, config: this.config, env, synapseContext, metadata: { originalArgs: args, timestamp: new Date().toISOString(), requestId: this.generateRequestId() } }; // Route the request const decision = await this.router.route(routingContext); // Determine provider and context source const provider = this.extractProvider(decision.model); const contextSource = this.determineContextSource(decision.source, synapseContext); // Create result const result = { model: decision.model, provider, decision, metadata: { projectId: synapseContext.projectId, agentId: synapseContext.agentId, agentType: synapseContext.agentType, originalModel: parsedArgs.model, routingReason: decision.reason, contextSource, timestamp: new Date().toISOString() } }; // Track usage await this.trackUsage(result, routingContext); // Emit routing event this.emit('routing', result); return result; } catch (error) { (0, log_1.log)('Error routing Claude request:', error); this.emit('error', error); throw error; } } /** * Track usage data for Synapse integration */ async trackUsage(routingResult, context) { const usageData = { id: this.generateUsageId(), timestamp: new Date(), projectId: routingResult.metadata.projectId, agentId: routingResult.metadata.agentId, agentType: routingResult.metadata.agentType, model: routingResult.decision.model, originalModel: routingResult.metadata.originalModel, routedModel: routingResult.model, provider: routingResult.provider, tokenCount: routingResult.decision.tokenCount || 0, cost: this.calculateCost(routingResult.decision.tokenCount || 0, routingResult.model), routingReason: routingResult.decision.reason, contextSource: routingResult.metadata.contextSource, metadata: { ...routingResult.metadata, requestId: context.metadata?.requestId } }; await this.usageTracker.trackUsage(usageData); this.emit('usage', usageData); } /** * Get usage statistics for a project */ async getProjectUsage(projectId, timeRange) { return this.usageTracker.getUsageStats({ projectId, timeRange }); } /** * Get usage statistics for an agent */ async getAgentUsage(agentId, timeRange) { return this.usageTracker.getUsageStats({ agentId, timeRange }); } /** * Get cost breakdown for a project */ async getProjectCostBreakdown(projectId) { return this.usageTracker.getCostBreakdown({ projectId }); } /** * Update configuration */ async updateConfig(config) { this.config = { ...this.config, ...config }; await this.router.updateConfig(this.config); this.emit('config-updated', this.config); } /** * Get current configuration */ getConfig() { return { ...this.config }; } /** * Get Synapse context from environment */ extractSynapseContext(env = process.env) { return this.contextExtractor.extractSynapseContext(env); } /** * Test routing with given context */ async testRouting(message, synapseContext = {}, options = {}) { const args = [ '--message', message, ...(options.model ? ['--model', options.model] : []), ...(options.thinking ? ['--thinking'] : []) ]; const testEnv = { ...process.env, ...(synapseContext.projectId ? { SYNAPSE_PROJECT_ID: synapseContext.projectId } : {}), ...(synapseContext.agentId ? { SYNAPSE_AGENT_ID: synapseContext.agentId } : {}), ...(synapseContext.agentType ? { SYNAPSE_AGENT_TYPE: synapseContext.agentType } : {}), ...(synapseContext.projectModelConfig ? { SYNAPSE_PROJECT_MODEL_CONFIG: synapseContext.projectModelConfig } : {}), ...(synapseContext.agentModelConfig ? { SYNAPSE_AGENT_MODEL_CONFIG: synapseContext.agentModelConfig } : {}) }; return this.routeClaudeRequest(args, testEnv); } /** * Parse Claude CLI arguments */ parseClaudeArgs(args) { const parsed = { model: 'claude-3-5-sonnet-20241022', messages: [], thinking: false, systemPrompt: undefined }; for (let i = 0; i < args.length; i++) { const arg = args[i]; switch (arg) { case '-m': case '--model': parsed.model = args[++i]; break; case '-p': case '--prompt': parsed.systemPrompt = args[++i]; break; case '--message': parsed.messages.push({ role: 'user', content: args[++i] }); break; case '--thinking': parsed.thinking = true; break; default: if (!arg.startsWith('-') && parsed.messages.length === 0) { parsed.messages.push({ role: 'user', content: arg }); } break; } } return parsed; } /** * Estimate token count for request */ async estimateTokenCount(parsedArgs, synapseContext) { let totalTokens = 0; // Count message tokens if (parsedArgs.messages && parsedArgs.messages.length > 0) { const result = await token_counter_1.tokenCounter.countMessagesTokens(parsedArgs.messages, parsedArgs.model); totalTokens += result.count; } // Count system prompt tokens if (parsedArgs.systemPrompt) { const result = await token_counter_1.tokenCounter.countTokens(parsedArgs.systemPrompt, parsedArgs.model); totalTokens += result.count; } // Add overhead for Synapse context if (synapseContext.projectId || synapseContext.agentId) { totalTokens += 50; // Estimated overhead } return totalTokens; } /** * Extract provider from model name */ extractProvider(model) { if (model.includes('claude') || model.includes('anthropic')) { return 'anthropic'; } if (model.includes('gpt') || model.includes('openai')) { return 'openai'; } if (model.includes('gemini')) { return 'google'; } return 'unknown'; } /** * Determine context source based on routing decision */ determineContextSource(routingSource, synapseContext) { if (routingSource === 'synapse-context') { if (synapseContext.agentModelConfig) { return 'agent'; } if (synapseContext.projectModelConfig) { return 'project'; } } return 'fallback'; } /** * Calculate cost for usage */ calculateCost(tokenCount, model) { // Simplified cost calculation - should be enhanced with actual pricing const baseCostPerToken = 0.000001; // $0.000001 per token let multiplier = 1; if (model.includes('opus')) { multiplier = 15; } else if (model.includes('sonnet')) { multiplier = 3; } else if (model.includes('haiku')) { multiplier = 0.25; } return tokenCount * baseCostPerToken * multiplier; } /** * Generate unique request ID */ generateRequestId() { return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } /** * Generate unique usage ID */ generateUsageId() { return `usage_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } /** * Set up event forwarding */ setupEventForwarding() { this.router.on('routing-decision', (data) => { this.emit('router-decision', data); }); this.router.on('error', (error) => { this.emit('router-error', error); }); this.usageTracker.on('usage-tracked', (data) => { this.emit('usage-tracked', data); }); this.usageTracker.on('error', (error) => { this.emit('usage-error', error); }); } /** * Cleanup resources */ async cleanup() { (0, log_1.log)('Cleaning up Synapse integration...'); await this.router.cleanup(); await this.usageTracker.cleanup(); this.emit('cleanup'); (0, log_1.log)('Synapse integration cleaned up'); } } exports.SynapseIntegration = SynapseIntegration; //# sourceMappingURL=synapse.js.map