config.ts•6.6 kB
import { z } from 'zod';
// Configuration schema with validation
const ConfigSchema = z.object({
// GitHub Configuration
github: z.object({
token: z.string().optional(),
apiUrl: z.string().default('https://api.github.com'),
maxRetries: z.number().default(3),
retryDelay: z.number().default(1000),
rateLimitBuffer: z.number().default(10),
}),
// OpenRouter/AI Configuration
openrouter: z.object({
apiKey: z.string().optional(),
apiUrl: z.string().default('https://openrouter.ai/api/v1'),
defaultModel: z.string().default('anthropic/claude-3.5-sonnet'),
maxRetries: z.number().default(3),
timeout: z.number().default(30000),
}),
// Response Management
response: z.object({
maxTokens: z.number().default(25000),
maxFileContentLength: z.number().default(1000),
chunkSizes: z.object({
small: z.object({
tokens: z.number().default(10000),
fileContent: z.number().default(500),
filesPerChunk: z.number().default(10),
}),
medium: z.object({
tokens: z.number().default(20000),
fileContent: z.number().default(1000),
filesPerChunk: z.number().default(20),
}),
large: z.object({
tokens: z.number().default(40000),
fileContent: z.number().default(2000),
filesPerChunk: z.number().default(40),
}),
}),
}),
// Cache Configuration
cache: z.object({
enabled: z.boolean().default(true),
ttl: z.number().default(300000), // 5 minutes
maxSize: z.number().default(100),
}),
// Logging Configuration
logging: z.object({
level: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
enableTimestamps: z.boolean().default(true),
enableColors: z.boolean().default(true),
}),
// Processing Limits
limits: z.object({
maxFiles: z.number().default(100),
maxFileSize: z.number().default(1024 * 1024), // 1MB
maxProcessingTime: z.number().default(60000), // 1 minute
maxConcurrentRequests: z.number().default(5),
}),
});
export type Config = z.infer<typeof ConfigSchema>;
// Environment variable mappings
const ENV_MAPPINGS = {
'GITHUB_TOKEN': 'github.token',
'GITHUB_API_URL': 'github.apiUrl',
'OPENROUTER_API_KEY': 'openrouter.apiKey',
'OPENROUTER_API_URL': 'openrouter.apiUrl',
'OPENAI_MODEL': 'openrouter.defaultModel',
'MAX_RESPONSE_TOKENS': 'response.maxTokens',
'MAX_FILE_CONTENT_LENGTH': 'response.maxFileContentLength',
'CACHE_ENABLED': 'cache.enabled',
'CACHE_TTL': 'cache.ttl',
'LOG_LEVEL': 'logging.level',
'MAX_FILES': 'limits.maxFiles',
'MAX_FILE_SIZE': 'limits.maxFileSize',
'MAX_PROCESSING_TIME': 'limits.maxProcessingTime',
};
// Helper to set nested object property
function setNestedProperty(obj: any, path: string, value: any) {
const keys = path.split('.');
let current = obj;
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
if (!(key in current)) {
current[key] = {};
}
current = current[key];
}
const finalKey = keys[keys.length - 1];
current[finalKey] = value;
}
// Helper to parse environment variable value
function parseEnvValue(value: string): any {
// Try to parse as number
if (/^\d+$/.test(value)) {
return parseInt(value, 10);
}
// Try to parse as boolean
if (value.toLowerCase() === 'true') return true;
if (value.toLowerCase() === 'false') return false;
// Return as string
return value;
}
// Load configuration from environment variables
function loadConfigFromEnv(): Partial<Config> {
const config: any = {};
for (const [envKey, configPath] of Object.entries(ENV_MAPPINGS)) {
const envValue = process.env[envKey];
if (envValue) {
setNestedProperty(config, configPath, parseEnvValue(envValue));
}
}
return config;
}
// Create and validate configuration
export function createConfig(): Config {
const envConfig = loadConfigFromEnv();
// Provide defaults for missing configuration sections
const configWithDefaults = {
github: {
token: process.env.GITHUB_TOKEN || '',
...envConfig.github
},
openrouter: {
apiKey: process.env.OPENROUTER_API_KEY || '',
defaultModel: 'anthropic/claude-3.5-sonnet',
...envConfig.openrouter
},
response: {
maxTokens: 25000,
maxFileContentLength: 5000,
chunkSizes: {
small: { filesPerChunk: 5, fileContent: 1000 },
medium: { filesPerChunk: 10, fileContent: 2000 },
large: { filesPerChunk: 20, fileContent: 5000 }
},
...envConfig.response
},
cache: {
enabled: true,
ttl: 3600,
...envConfig.cache
},
logging: {
level: 'info',
enableTimestamps: true,
enableColors: true,
...envConfig.logging
},
limits: {
maxFiles: 100,
maxFileSize: 10 * 1024 * 1024, // 10MB
maxProcessingTime: 30000, // 30 seconds
maxConcurrentRequests: 10,
...envConfig.limits
}
};
// Validate the configuration
const config = ConfigSchema.parse(configWithDefaults);
// Validate required fields for functionality
const warnings: string[] = [];
if (!config.github.token && !config.openrouter.apiKey) {
warnings.push('Warning: Neither GITHUB_TOKEN nor OPENROUTER_API_KEY is set. Some features may be limited.');
}
if (warnings.length > 0) {
console.warn(warnings.join('\n'));
}
return config;
}
// Global configuration instance
let globalConfig: Config | null = null;
export function getConfig(): Config {
if (!globalConfig) {
globalConfig = createConfig();
}
return globalConfig;
}
// Configuration validation helper
export function validateConfig(config: Partial<Config>): boolean {
try {
ConfigSchema.parse(config);
return true;
} catch (error) {
console.error('Configuration validation error:', error);
return false;
}
}
// Get model aliases for user-friendly names
export function getModelAliases(): Record<string, string> {
return {
'claude-3-sonnet': 'anthropic/claude-3.5-sonnet',
'claude-3-opus': 'anthropic/claude-3-opus',
'claude-3-haiku': 'anthropic/claude-3-haiku',
'gpt-4': 'openai/gpt-4',
'gpt-4-turbo': 'openai/gpt-4-turbo',
'gpt-4o': 'openai/gpt-4o',
'gpt-4o-mini': 'openai/gpt-4o-mini',
'gemini-pro': 'google/gemini-pro',
'gemini-1.5-pro': 'google/gemini-1.5-pro',
};
}
// Resolve model alias to full model name
export function resolveModelAlias(model: string): string {
const aliases = getModelAliases();
return aliases[model] || model;
}