@tehreet/conduit
Version:
LLM API gateway with intelligent routing, robust process management, and health monitoring
333 lines • 12 kB
JavaScript
"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