Spaces:
Sleeping
Sleeping
| import { z } from 'zod'; | |
| import fs from 'fs/promises'; | |
| import path from 'path'; | |
| import { fileURLToPath } from 'url'; | |
| const __dirname = path.dirname(fileURLToPath(import.meta.url)); | |
| const ConfigSchema = z.object({ | |
| github: z.object({ | |
| token: z.string().min(10, 'GITHUB_TOKEN must be a valid token'), | |
| owner: z.string().min(1, 'GITHUB_OWNER is required'), | |
| repo: z.string().min(1, 'GITHUB_REPO is required'), | |
| }), | |
| ai: z.object({ | |
| provider: z.enum(['ollama', 'huggingface', 'groq', 'openrouter', 'fallback']).default('fallback'), | |
| ollama: z.object({ | |
| model: z.string().default('llama3.2'), | |
| baseUrl: z.string().default('http://localhost:11434'), | |
| timeout: z.number().min(1000).max(120000).default(60000), | |
| maxRetries: z.number().min(0).max(10).default(3), | |
| }), | |
| huggingface: z.object({ | |
| apiKey: z.string().default(''), | |
| model: z.string().default('mistralai/Mistral-7B-Instruct-v0.2'), | |
| timeout: z.number().min(1000).max(120000).default(60000), | |
| maxRetries: z.number().min(0).max(10).default(3), | |
| }), | |
| groq: z.object({ | |
| apiKey: z.string().default(''), | |
| model: z.string().default('llama-3.1-8b-instant'), | |
| timeout: z.number().min(1000).max(120000).default(30000), | |
| maxRetries: z.number().min(0).max(10).default(3), | |
| }), | |
| openrouter: z.object({ | |
| apiKey: z.string().default(''), | |
| model: z.string().default('meta-llama/llama-3.1-8b-instruct:free'), | |
| timeout: z.number().min(1000).max(120000).default(30000), | |
| maxRetries: z.number().min(0).max(10).default(3), | |
| }), | |
| }), | |
| schedule: z.object({ | |
| timezone: z.string().default('Africa/Cairo'), | |
| noWorkHoursRestriction: z.boolean().default(true), | |
| }), | |
| activity: z.object({ | |
| minIntervalMinutes: z.number().min(1).max(1440).default(5), | |
| maxIntervalMinutes: z.number().min(1).max(1440).default(45), | |
| maxCommitsPerSession: z.number().min(1).max(50).default(8), | |
| maxFilesPerCommit: z.number().min(1).max(20).default(3), | |
| bugProbability: z.number().min(0).max(1).default(0.15), | |
| typoProbability: z.number().min(0).max(1).default(0.1), | |
| maxActionsPerCycle: z.number().min(1).max(10).default(3), | |
| }), | |
| daemon: z.object({ | |
| enabled: z.boolean().default(true), | |
| checkIntervalMinutes: z.number().min(1).max(120).default(10), | |
| maxCyclesBeforeRestart: z.number().min(10).max(1000).default(100), | |
| }), | |
| memory: z.object({ | |
| persistToFile: z.boolean().default(true), | |
| persistIntervalMinutes: z.number().min(1).max(60).default(15), | |
| maxHistorySize: z.number().min(100).max(10000).default(500), | |
| maxDecisionsSize: z.number().min(50).max(5000).default(200), | |
| }), | |
| logging: z.object({ | |
| level: z.enum(['debug', 'info', 'warn', 'error']).default('info'), | |
| file: z.string().default('logs/simulator.log'), | |
| maxFileSize: z.number().min(1000000).max(100000000).default(10000000), | |
| }), | |
| }); | |
| export function loadConfig() { | |
| const rawConfig = { | |
| github: { | |
| token: process.env.GITHUB_TOKEN, | |
| owner: process.env.GITHUB_OWNER, | |
| repo: process.env.GITHUB_REPO, | |
| }, | |
| ai: { | |
| provider: process.env.AI_PROVIDER || 'fallback', | |
| ollama: { | |
| model: process.env.OLLAMA_MODEL || 'llama3.2', | |
| baseUrl: process.env.OLLAMA_BASE_URL || 'http://localhost:11434', | |
| timeout: parseInt(process.env.OLLAMA_TIMEOUT) || 60000, | |
| maxRetries: parseInt(process.env.OLLAMA_MAX_RETRIES) || 3, | |
| }, | |
| huggingface: { | |
| apiKey: process.env.HUGGINGFACE_API_KEY || '', | |
| model: process.env.HUGGINGFACE_MODEL || 'mistralai/Mistral-7B-Instruct-v0.2', | |
| timeout: parseInt(process.env.HUGGINGFACE_TIMEOUT) || 60000, | |
| maxRetries: parseInt(process.env.HUGGINGFACE_MAX_RETRIES) || 3, | |
| }, | |
| groq: { | |
| apiKey: process.env.GROQ_API_KEY || '', | |
| model: process.env.GROQ_MODEL || 'llama-3.1-8b-instant', | |
| timeout: parseInt(process.env.GROQ_TIMEOUT) || 30000, | |
| maxRetries: parseInt(process.env.GROQ_MAX_RETRIES) || 3, | |
| }, | |
| openrouter: { | |
| apiKey: process.env.OPENROUTER_API_KEY || '', | |
| model: process.env.OPENROUTER_MODEL || 'meta-llama/llama-3.1-8b-instruct:free', | |
| timeout: parseInt(process.env.OPENROUTER_TIMEOUT) || 30000, | |
| maxRetries: parseInt(process.env.OPENROUTER_MAX_RETRIES) || 3, | |
| }, | |
| }, | |
| schedule: { | |
| timezone: process.env.TIMEZONE || 'Africa/Cairo', | |
| noWorkHoursRestriction: process.env.NO_WORK_HOURS_RESTRICTION !== 'false', | |
| }, | |
| activity: { | |
| minIntervalMinutes: _parseInt(process.env.MIN_ACTIVITY_INTERVAL_MINUTES, 5), | |
| maxIntervalMinutes: _parseInt(process.env.MAX_ACTIVITY_INTERVAL_MINUTES, 45), | |
| maxCommitsPerSession: _parseInt(process.env.MAX_COMMITS_PER_SESSION, 8), | |
| maxFilesPerCommit: _parseInt(process.env.MAX_FILES_PER_COMMIT, 3), | |
| bugProbability: _parseFloat(process.env.BUG_PROBABILITY, 0.15), | |
| typoProbability: _parseFloat(process.env.TYPO_PROBABILITY, 0.1), | |
| maxActionsPerCycle: _parseInt(process.env.MAX_ACTIONS_PER_CYCLE, 3), | |
| }, | |
| daemon: { | |
| enabled: process.env.DAEMON_ENABLED !== 'false', | |
| checkIntervalMinutes: _parseInt(process.env.DAEMON_CHECK_INTERVAL_MINUTES, 10), | |
| maxCyclesBeforeRestart: _parseInt(process.env.MAX_CYCLES_BEFORE_RESTART, 100), | |
| }, | |
| memory: { | |
| persistToFile: process.env.MEMORY_PERSIST_TO_FILE !== 'false', | |
| persistIntervalMinutes: _parseInt(process.env.MEMORY_PERSIST_INTERVAL_MINUTES, 15), | |
| maxHistorySize: _parseInt(process.env.MEMORY_MAX_HISTORY_SIZE, 500), | |
| maxDecisionsSize: _parseInt(process.env.MEMORY_MAX_DECISIONS_SIZE, 200), | |
| }, | |
| logging: { | |
| level: process.env.LOG_LEVEL || 'info', | |
| file: process.env.LOG_FILE || 'logs/simulator.log', | |
| maxFileSize: _parseInt(process.env.LOG_MAX_FILE_SIZE, 10000000), | |
| }, | |
| }; | |
| try { | |
| return ConfigSchema.parse(rawConfig); | |
| } catch (error) { | |
| const formattedErrors = error.errors | |
| .map(e => ` - ${e.path.join('.')}: ${e.message}`) | |
| .join('\n'); | |
| throw new Error(`Invalid configuration:\n${formattedErrors}`); | |
| } | |
| } | |
| export async function saveConfig(config, filePath = '.env') { | |
| const envContent = `GITHUB_TOKEN=${config.github.token} | |
| GITHUB_OWNER=${config.github.owner} | |
| GITHUB_REPO=${config.github.repo} | |
| AI_PROVIDER=${config.ai.provider} | |
| OLLAMA_MODEL=${config.ai.ollama.model} | |
| OLLAMA_BASE_URL=${config.ai.ollama.baseUrl} | |
| HUGGINGFACE_API_KEY=${config.ai.huggingface.apiKey} | |
| HUGGINGFACE_MODEL=${config.ai.huggingface.model} | |
| TIMEZONE=${config.schedule.timezone} | |
| NO_WORK_HOURS_RESTRICTION=true | |
| MIN_ACTIVITY_INTERVAL_MINUTES=${config.activity.minIntervalMinutes} | |
| MAX_ACTIVITY_INTERVAL_MINUTES=${config.activity.maxIntervalMinutes} | |
| MAX_COMMITS_PER_SESSION=${config.activity.maxCommitsPerSession} | |
| MAX_FILES_PER_COMMIT=${config.activity.maxFilesPerCommit} | |
| BUG_PROBABILITY=${config.activity.bugProbability} | |
| TYPO_PROBABILITY=${config.activity.typoProbability} | |
| DAEMON_ENABLED=${config.daemon.enabled} | |
| DAEMON_CHECK_INTERVAL_MINUTES=${config.daemon.checkIntervalMinutes} | |
| MEMORY_PERSIST_TO_FILE=${config.memory.persistToFile} | |
| MEMORY_PERSIST_INTERVAL_MINUTES=${config.memory.persistIntervalMinutes} | |
| LOG_LEVEL=${config.logging.level} | |
| LOG_FILE=${config.logging.file} | |
| `; | |
| await fs.writeFile(filePath, envContent, 'utf8'); | |
| } | |
| export function validateConfig(config) { | |
| try { | |
| ConfigSchema.parse(config); | |
| return { valid: true, errors: [] }; | |
| } catch (error) { | |
| return { | |
| valid: false, | |
| errors: error.errors.map(e => `${e.path.join('.')}: ${e.message}`), | |
| }; | |
| } | |
| } | |
| function _parseInt(value, defaultValue) { | |
| if (value === undefined || value === null || value === '') return defaultValue; | |
| const parsed = parseInt(value, 10); | |
| return isNaN(parsed) ? defaultValue : parsed; | |
| } | |
| function _parseFloat(value, defaultValue) { | |
| if (value === undefined || value === null || value === '') return defaultValue; | |
| const parsed = parseFloat(value); | |
| return isNaN(parsed) ? defaultValue : parsed; | |
| } | |
| export default loadConfig; | |