gravityyy-proxyyy / src /config.js
bardd's picture
Fix gemini-3.1-pro-high by routing to pro-low with thinkingLevel high.
4badc3b
Raw
History Blame Contribute Delete
6.35 kB
import './bootstrap/load-config-env.js';
import fs from 'fs';
import path from 'path';
import os from 'os';
import { logger } from './utils/logger.js';
function isObject(item) {
return (item && typeof item === 'object' && !Array.isArray(item));
}
const DENIED_KEYS = ['__proto__', 'constructor', 'prototype'];
function deepMerge(target, source) {
const output = { ...target };
if (isObject(target) && isObject(source)) {
Object.keys(source).forEach(key => {
if (DENIED_KEYS.includes(key)) return;
if (isObject(source[key])) {
if (!(key in target)) {
Object.assign(output, { [key]: source[key] });
} else {
output[key] = deepMerge(target[key], source[key]);
}
} else {
Object.assign(output, { [key]: source[key] });
}
});
}
return output;
}
// Default config
const DEFAULT_CONFIG = {
apiKey: '',
webuiPassword: '',
debug: false,
devMode: false,
logLevel: 'info',
maxRetries: 5,
retryBaseMs: 1000,
retryMaxMs: 30000,
persistTokenCache: false,
defaultCooldownMs: 10000, // 10 seconds
maxWaitBeforeErrorMs: 120000, // 2 minutes
maxAccounts: 10, // Maximum number of accounts allowed
globalQuotaThreshold: 0, // 0 = disabled, 0.01-0.99 = minimum quota fraction before switching accounts
requestThrottlingEnabled: false, // Opt-in: enable delay before Google API requests
requestDelayMs: 200, // Delay in ms when throttling enabled (100-5000ms)
// Rate limit handling (matches opencode-antigravity-auth)
rateLimitDedupWindowMs: 2000, // 2 seconds - prevents concurrent retry storms
maxConsecutiveFailures: 3, // Before applying extended cooldown
extendedCooldownMs: 60000, // 1 minute extended cooldown
maxCapacityRetries: 5, // Max retries for capacity exhaustion
switchAccountDelayMs: 5000, // Delay before switching accounts on rate limit
capacityBackoffTiersMs: [5000, 10000, 20000, 30000, 60000], // Progressive backoff tiers for capacity exhaustion
modelMapping: {},
// Account selection strategy configuration
accountSelection: {
strategy: 'hybrid', // 'sticky' | 'round-robin' | 'hybrid'
// Hybrid strategy tuning (optional - sensible defaults)
healthScore: {
initial: 70, // Starting score for new accounts
successReward: 1, // Points on successful request
rateLimitPenalty: -10, // Points on rate limit
failurePenalty: -20, // Points on other failures
recoveryPerHour: 10, // Passive recovery rate (matches health-tracker.js)
minUsable: 50, // Minimum score to be selected
maxScore: 100 // Maximum score cap
},
tokenBucket: {
maxTokens: 50, // Maximum token capacity
tokensPerMinute: 6, // Regeneration rate
initialTokens: 50 // Starting tokens
},
quota: {
lowThreshold: 0.10, // 10% - reduce score
criticalThreshold: 0.05, // 5% - exclude from candidates
staleMs: 300000 // 5 min - max age of quota data to trust
},
weights: {
health: 2, // Weight for health score component
tokens: 5, // Weight for token bucket component
quota: 3, // Weight for quota awareness component
lru: 0.1 // Weight for LRU freshness component
}
}
};
// Config locations
const HOME_DIR = os.homedir();
const CONFIG_DIR = path.join(HOME_DIR, '.config', 'antigravity-proxy');
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
// Ensure config dir exists
if (!fs.existsSync(CONFIG_DIR)) {
try {
fs.mkdirSync(CONFIG_DIR, { recursive: true });
} catch (err) {
// Ignore
}
}
// Load config
let config = { ...DEFAULT_CONFIG };
function loadConfig() {
try {
// Env vars take precedence for initial defaults, but file overrides them if present?
// Usually Env > File > Default.
if (fs.existsSync(CONFIG_FILE)) {
const fileContent = fs.readFileSync(CONFIG_FILE, 'utf8');
const userConfig = JSON.parse(fileContent);
config = deepMerge(DEFAULT_CONFIG, userConfig);
} else {
// Try looking in current dir for config.json as fallback
const localConfigPath = path.resolve('config.json');
if (fs.existsSync(localConfigPath)) {
const fileContent = fs.readFileSync(localConfigPath, 'utf8');
const userConfig = JSON.parse(fileContent);
config = deepMerge(DEFAULT_CONFIG, userConfig);
}
}
// Environment overrides
const envApiKey = process.env.API_KEY || process.env.PROXY_API_KEY;
if (envApiKey) config.apiKey = envApiKey;
if (process.env.WEBUI_PASSWORD) config.webuiPassword = process.env.WEBUI_PASSWORD;
if (process.env.DEBUG === 'true') config.debug = true;
if (process.env.DEV_MODE === 'true') config.devMode = true;
// Backward compat: debug implies devMode
if (config.debug && !config.devMode) config.devMode = true;
} catch (error) {
logger.error('[Config] Error loading config:', error);
}
}
// Initial load
loadConfig();
export function getPublicConfig() {
// Create a deep copy and redact sensitive fields
const publicConfig = JSON.parse(JSON.stringify(config));
// Redact sensitive values
if (publicConfig.webuiPassword) publicConfig.webuiPassword = '********';
if (publicConfig.apiKey) publicConfig.apiKey = '********';
return publicConfig;
}
export function saveConfig(updates) {
try {
// Apply updates (deep merge to preserve nested configs)
config = deepMerge(config, updates);
// Save to disk
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf8');
return true;
} catch (error) {
logger.error('[Config] Failed to save config:', error);
return false;
}
}
export { config };