const DEFAULTS = { parseApiHost: 'https://server.fmode.cn', parseAppId: 'ncloudmaster', parseBaseUrl: 'https://server.fmode.cn/api/functions', douyinApiBaseUrl: 'https://server.fmode.cn/api/voc-social', transcriptionGatewayBaseUrl: 'https://server.fmode.cn/api/apig/transcription', qiniuUploadUrl: 'https://up-z2.qiniup.com', volcSpeechBaseUrl: 'https://openspeech.bytedance.com', volcTtsProxyBaseUrl: 'https://server.fmode.cn/api/volcengine/tts', llmBaseUrl: 'http://server.fmode.cn:9999', quicklyCallbackUrl: 'https://server.fmode.cn/api/functions/cut/onemerge', quicklyRelayUrl: 'https://server.fmode.cn/api/functions', }; function env(name, fallback = '') { const value = process.env[name]; return value === undefined || value === null || value === '' ? fallback : String(value).trim(); } function firstEnv(names, fallback = '') { for (const name of names) { const value = env(name); if (value) return value; } return fallback; } function stripTrailingSlash(value) { return String(value || '').replace(/\/+$/, ''); } function normalizeBearerToken(token) { const value = String(token || '').trim(); if (!value) return ''; return /^Bearer\s+/i.test(value) ? value : `Bearer ${value}`; } function requireSecret(value, label, names) { if (String(value || '').trim()) return String(value).trim(); const list = Array.isArray(names) ? names.join('、') : names; const err = new Error(`未配置 ${label},请设置 ${list}`); err.statusCode = 400; throw err; } function redact(value) { const raw = String(value || '').trim(); if (!raw) return ''; if (raw.length <= 10) return `${raw.slice(0, 2)}***`; return `${raw.slice(0, 6)}***${raw.slice(-4)}`; } const config = { port: Number(env('PORT', '3000')) || 3000, parseApiHost: stripTrailingSlash(env('PARSE_API_HOST', DEFAULTS.parseApiHost)), parseAppId: env('PARSE_APP_ID', DEFAULTS.parseAppId), parseBaseUrl: stripTrailingSlash(env('PARSE_BASE_URL', DEFAULTS.parseBaseUrl)), douyinApiBaseUrl: stripTrailingSlash(env('DOUYIN_API_BASE_URL', DEFAULTS.douyinApiBaseUrl)), douyinGatewayMaxAttempts: Math.max(1, Number(env('DOUYIN_GATEWAY_MAX_ATTEMPTS', '4')) || 4), transcriptionGatewayBaseUrl: stripTrailingSlash(env('IFLYTEK_GATEWAY_BASE_URL', DEFAULTS.transcriptionGatewayBaseUrl)), qiniu: { accessKey: firstEnv(['QINIU_AK', 'QINIU_ACCESS_KEY']), secretKey: firstEnv(['QINIU_SK', 'QINIU_SECRET_KEY']), bucket: env('QINIU_BUCKET', 'nova-repos'), cdnDomain: env('QINIU_CDN_DOMAIN', env('QINIU_DOMAIN', 'https://repos.fmode.cn')), cdnPrefix: env('QINIU_CDN_PREFIX', 'x/openclaw-skills'), uploadUrl: env('QINIU_UPLOAD_URL', DEFAULTS.qiniuUploadUrl), }, volc: { speechBaseUrl: stripTrailingSlash(env('VOLC_SPEECH_BASE_URL', DEFAULTS.volcSpeechBaseUrl)), ttsProxyBaseUrl: stripTrailingSlash(env('VOLC_TTS_PROXY_BASE_URL', DEFAULTS.volcTtsProxyBaseUrl)), speechApiKey: firstEnv(['VOLC_SPEECH_API_KEY', 'BYTEDANCE_SPEECH_API_KEY', 'VOICE_CREATION_API_KEY']), ttsResourceId: env('VOLC_TTS_RESOURCE_ID'), speechAppKey: firstEnv(['VOLC_SPEECH_APP_KEY', 'VOLC_SPEECH_APP_ID']), speechAccessKey: env('VOLC_SPEECH_ACCESS_KEY'), }, quickly: { appKey: env('QUICKLY_APP_KEY'), appSecret: env('QUICKLY_APP_SECRET'), accountId: env('QUICKLY_ACCOUNT_ID'), callbackUrl: env('QUICKLY_CALLBACK_URL', DEFAULTS.quicklyCallbackUrl), relayUrl: env('QUICKLY_RELAY_URL', DEFAULTS.quicklyRelayUrl), }, llm: { baseUrl: stripTrailingSlash(env('LLM_BASE_URL', DEFAULTS.llmBaseUrl)), apiKey: env('LLM_API_KEY'), }, voice: { defaultToken: normalizeBearerToken(firstEnv(['VOLC_TTS_TOKEN', 'VOICE_TOKEN'])), }, }; function getVocToken() { return firstEnv([ 'DOUYIN_API_TOKEN', 'VOC_TOKEN', 'TRANSCRIPTION_VOC_TOKEN', 'VOICE_TOKEN', 'OPENCLAW_VOC_TOKEN', 'VOC_SOCIAL_TOKEN', ]); } module.exports = { ...config, getVocToken, normalizeBearerToken, requireSecret, redact, };