UNPKG

@tehreet/conduit

Version:

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

243 lines 9.62 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SynapsePlugin = void 0; const log_1 = require("../utils/log"); class SynapsePlugin { constructor() { this.name = 'synapse-plugin'; this.version = '1.0.0'; } async customRouter(context) { (0, log_1.log)(`[SynapsePlugin] customRouter called with context:`, { hasRequest: !!context.request, hasRequestBody: !!context.requestBody, hasSynapseContext: !!context.synapseContext, requestUrl: context.request?.url }); // First try to extract from HTTP request body let synapseContextData = this.extractSynapseContextFromRequest(context); // Fallback to environment variables if not in request if (!synapseContextData) { synapseContextData = this.extractSynapseContextData(context.env); } if (!synapseContextData) { (0, log_1.log)(`[SynapsePlugin] No synapseContext found, returning null`); return null; } const { projectConfig, agentConfig, routingRules } = synapseContextData; // Evaluate routing rules in order if (routingRules) { for (const rule of routingRules) { if (this.evaluateRule(rule, context)) { (0, log_1.log)(`Synapse routing rule '${rule.name}' matched, using model: ${rule.targetModel}`); return { model: rule.targetModel, source: 'synapse-plugin', reason: `Synapse routing rule '${rule.name}' matched`, tokenCount: context.tokenCount, metadata: { ruleName: rule.name, ruleCondition: rule.condition } }; } } } // Return configured model if (agentConfig && !agentConfig.useProjectDefaults && agentConfig.overrideModel) { (0, log_1.log)(`Using Synapse agent override model: ${agentConfig.overrideModel}`); return { model: agentConfig.overrideModel, source: 'synapse-plugin', reason: 'Synapse agent override model', tokenCount: context.tokenCount, metadata: { configType: 'agent-override' } }; } if (projectConfig && projectConfig.enabled && projectConfig.defaultModel) { (0, log_1.log)(`Using Synapse project default model: ${projectConfig.defaultModel}`); return { model: projectConfig.defaultModel, source: 'synapse-plugin', reason: 'Synapse project default model', tokenCount: context.tokenCount, metadata: { configType: 'project-default' } }; } return null; // Let Conduit handle default routing } async onModelSelected(model, context) { // Send telemetry back to Synapse const telemetryEndpoint = context.env?.SYNAPSE_TELEMETRY_ENDPOINT; if (telemetryEndpoint) { try { const response = await fetch(telemetryEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ event: 'model_selected', model, projectId: context.env.SYNAPSE_PROJECT_ID, agentId: context.env.SYNAPSE_AGENT_ID, timestamp: new Date().toISOString(), }), }); if (!response.ok) { (0, log_1.log)(`Synapse telemetry failed: ${response.status} ${response.statusText}`); } } catch (error) { // Don't fail on telemetry errors, just log them (0, log_1.log)(`Synapse telemetry error:`, error); } } } extractSynapseContextFromRequest(context) { // Check multiple possible locations for synapseContext let synapseContext = null; // Try requestBody first (new field) if (context.requestBody?.synapseContext) { synapseContext = context.requestBody.synapseContext; (0, log_1.log)('Found synapseContext in requestBody'); } // Then try request.body else if (context.request?.body?.synapseContext) { synapseContext = context.request.body.synapseContext; (0, log_1.log)('Found synapseContext in request.body'); } // Try direct context else if (context.synapseContext) { synapseContext = context.synapseContext; (0, log_1.log)('Found synapseContext in context root'); } if (!synapseContext) { (0, log_1.log)('No synapseContext found in request'); return null; } try { // Handle both string and object formats const data = typeof synapseContext === 'string' ? JSON.parse(synapseContext) : synapseContext; (0, log_1.log)('Parsed synapseContext:', data); return data; } catch (error) { (0, log_1.log)('Failed to parse synapseContext:', error); return null; } } extractSynapseContextData(env) { // Check if we have a SYNAPSE_CONTEXT environment variable if (env.SYNAPSE_CONTEXT) { try { return JSON.parse(env.SYNAPSE_CONTEXT); } catch (error) { (0, log_1.log)('Error parsing SYNAPSE_CONTEXT:', error); } } // Fallback to individual environment variables const projectConfig = env.SYNAPSE_PROJECT_MODEL_CONFIG ? this.parseJsonSafely(env.SYNAPSE_PROJECT_MODEL_CONFIG) : undefined; const agentConfig = env.SYNAPSE_AGENT_MODEL_CONFIG ? this.parseJsonSafely(env.SYNAPSE_AGENT_MODEL_CONFIG) : undefined; if (projectConfig || agentConfig) { return { projectConfig, agentConfig }; } return null; } parseJsonSafely(jsonString) { try { return JSON.parse(jsonString); } catch (error) { (0, log_1.log)('Error parsing JSON:', error); return undefined; } } evaluateRule(rule, context) { try { // Simple condition evaluation - can be extended for more complex rules const condition = rule.condition.toLowerCase(); // Token count conditions if (condition.includes('tokencount')) { return this.evaluateTokenCountCondition(condition, context.tokenCount); } // Flags conditions if (condition.includes('flags.includes')) { return this.evaluateFlagsCondition(condition, context); } // Model type conditions if (condition.includes('model')) { return this.evaluateModelCondition(condition, context); } (0, log_1.log)(`Unknown condition in routing rule: ${condition}`); return false; } catch (error) { (0, log_1.log)(`Error evaluating routing rule '${rule.name}':`, error); return false; } } evaluateTokenCountCondition(condition, tokenCount) { // Extract number from condition like "tokenCount < 4000" const match = condition.match(/tokencount\s*([<>]=?)\s*(\d+)/); if (!match) return false; const operator = match[1]; const threshold = parseInt(match[2]); switch (operator) { case '<': return tokenCount < threshold; case '<=': return tokenCount <= threshold; case '>': return tokenCount > threshold; case '>=': return tokenCount >= threshold; default: return false; } } evaluateFlagsCondition(condition, context) { // Extract flag from condition like "flags.includes('--thinking')" const match = condition.match(/flags\.includes\(['"]([^'"]+)['"]\)/); if (!match) return false; const flag = match[1]; // Check if the flag exists in request body or other context if (context.request?.body?.thinking && flag === '--thinking') { return true; } // Could extend this to check other flags or request properties return false; } evaluateModelCondition(condition, context) { // Simple model matching - can be extended const requestedModel = context.request?.body?.model || ''; if (condition.includes('haiku') && requestedModel.includes('haiku')) { return true; } if (condition.includes('sonnet') && requestedModel.includes('sonnet')) { return true; } if (condition.includes('opus') && requestedModel.includes('opus')) { return true; } return false; } } exports.SynapsePlugin = SynapsePlugin; exports.default = SynapsePlugin; //# sourceMappingURL=synapse-plugin.js.map