UNPKG

cui-server

Version:

Web UI Agent Platform based on Claude Code

114 lines 5.07 kB
import Server from '@musistudio/llms'; import { createLogger } from './logger.js'; /** * Wrapper around the Claude Code Router server */ export class ClaudeRouterService { server; config; logger; port = 14001; // hardcoded 14xxx port constructor(config) { this.config = config; this.logger = createLogger('ClaudeRouterService'); } async initialize() { if (!this.config.enabled) { this.logger.debug('Router service is disabled in configuration'); return; } if (!this.config.providers || this.config.providers.length === 0) { this.logger.warn('Router enabled but no providers configured'); return; } this.logger.debug(`Router service initializing with ${this.config.providers.length} provider(s)`); try { this.server = new Server({ initialConfig: { providers: this.config.providers, Router: this.config.rules, HOST: '127.0.0.1', PORT: this.port } }); // Add routing transformation hook BEFORE the server starts // This hook runs BEFORE the @musistudio/llms preHandler that splits by comma this.server.addHook('preHandler', async (req, _reply) => { // Only process /v1/messages requests (Claude API format) if (!req.url.startsWith('/v1/messages') || req.method !== 'POST') { return; } try { const body = req.body; if (!body || !body.model) { return; } // Apply routing transformation based on rules let targetModel = body.model; // Check if we have specific rules for this model if (this.config.rules && typeof this.config.rules === 'object') { // Check for exact model match if (this.config.rules[body.model]) { targetModel = this.config.rules[body.model]; this.logger.debug(`Routing ${body.model} -> ${targetModel}`); } // Check for haiku background routing else if (body.model?.startsWith('claude-3-5-haiku') && this.config.rules.background) { targetModel = this.config.rules.background; this.logger.debug(`Routing haiku model ${body.model} -> ${targetModel}`); } // Check for thinking mode else if (body.thinking && this.config.rules.think) { targetModel = this.config.rules.think; this.logger.debug(`Routing thinking mode -> ${targetModel}`); } // Check for web search else if (Array.isArray(body.tools) && body.tools.some((tool) => tool.type?.startsWith('web_search')) && this.config.rules.webSearch) { targetModel = this.config.rules.webSearch; this.logger.debug(`Routing web search -> ${targetModel}`); } // Use default rule if available else if (this.config.rules.default) { targetModel = this.config.rules.default; this.logger.debug(`Routing default ${body.model} -> ${targetModel}`); } } // Update the model in the request body // This will be in "provider,model" format for @musistudio/llms body.model = targetModel; } catch (error) { this.logger.error('Error in router preHandler hook:', error); // Don't modify the request on error, let it pass through } }); await this.server.start(); this.logger.info('Claude Code Router started', { port: this.port }); } catch (error) { this.logger.error('Failed to start Claude Code Router', error); this.server = undefined; throw error; } } isEnabled() { return !!this.server; } getProxyUrl() { return `http://127.0.0.1:${this.port}`; } getProxyKey() { return 'router-managed'; } async stop() { if (this.server && typeof this.server.stop === 'function') { this.logger.debug('Stopping Claude Code Router...'); await this.server.stop(); this.server = undefined; this.logger.debug('Claude Code Router stopped successfully'); } } } //# sourceMappingURL=claude-router-service.js.map