File size: 8,323 Bytes
69b897d 75031b4 69b897d 75031b4 69b897d 75031b4 69b897d 75031b4 69b897d 75031b4 69b897d |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 |
#!/usr/bin/env node
/**
* 手动更新模型价格数据脚本
* 从价格镜像分支下载最新的模型价格和上下文窗口信息
*/
const fs = require('fs')
const path = require('path')
const https = require('https')
const crypto = require('crypto')
const pricingSource = require('../config/pricingSource')
// 颜色输出
const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[36m',
magenta: '\x1b[35m'
}
// 日志函数
const log = {
info: (msg) => console.log(`${colors.blue}[INFO]${colors.reset} ${msg}`),
success: (msg) => console.log(`${colors.green}[SUCCESS]${colors.reset} ${msg}`),
error: (msg) => console.error(`${colors.red}[ERROR]${colors.reset} ${msg}`),
warn: (msg) => console.warn(`${colors.yellow}[WARNING]${colors.reset} ${msg}`)
}
// 配置
const config = {
dataDir: path.join(process.cwd(), 'data'),
pricingFile: path.join(process.cwd(), 'data', 'model_pricing.json'),
hashFile: path.join(process.cwd(), 'data', 'model_pricing.sha256'),
pricingUrl: pricingSource.pricingUrl,
fallbackFile: path.join(
process.cwd(),
'resources',
'model-pricing',
'model_prices_and_context_window.json'
),
backupFile: path.join(process.cwd(), 'data', 'model_pricing.backup.json'),
timeout: 30000 // 30秒超时
}
// 创建数据目录
function ensureDataDir() {
if (!fs.existsSync(config.dataDir)) {
fs.mkdirSync(config.dataDir, { recursive: true })
log.info('Created data directory')
}
}
// 备份现有文件
function backupExistingFile() {
if (fs.existsSync(config.pricingFile)) {
try {
fs.copyFileSync(config.pricingFile, config.backupFile)
log.info('Backed up existing pricing file')
return true
} catch (error) {
log.warn(`Failed to backup existing file: ${error.message}`)
return false
}
}
return false
}
// 恢复备份
function restoreBackup() {
if (fs.existsSync(config.backupFile)) {
try {
fs.copyFileSync(config.backupFile, config.pricingFile)
log.info('Restored from backup')
return true
} catch (error) {
log.error(`Failed to restore backup: ${error.message}`)
return false
}
}
return false
}
// 下载价格数据
function downloadPricingData() {
return new Promise((resolve, reject) => {
log.info('正在从价格镜像分支拉取最新的模型价格数据...')
log.info(`拉取地址: ${config.pricingUrl}`)
const request = https.get(config.pricingUrl, (response) => {
if (response.statusCode !== 200) {
reject(new Error(`HTTP ${response.statusCode}: ${response.statusMessage}`))
return
}
let data = ''
let downloadedBytes = 0
response.on('data', (chunk) => {
data += chunk
downloadedBytes += chunk.length
// 显示下载进度
process.stdout.write(`\rDownloading... ${Math.round(downloadedBytes / 1024)}KB`)
})
response.on('end', () => {
process.stdout.write('\n') // 换行
try {
const jsonData = JSON.parse(data)
// 验证数据结构
if (typeof jsonData !== 'object' || Object.keys(jsonData).length === 0) {
throw new Error('Invalid pricing data structure')
}
// 保存到文件
const formattedJson = JSON.stringify(jsonData, null, 2)
fs.writeFileSync(config.pricingFile, formattedJson)
const hash = crypto.createHash('sha256').update(formattedJson).digest('hex')
fs.writeFileSync(config.hashFile, `${hash}\n`)
const modelCount = Object.keys(jsonData).length
const fileSize = Math.round(fs.statSync(config.pricingFile).size / 1024)
log.success(`Downloaded pricing data for ${modelCount} models (${fileSize}KB)`)
// 显示一些统计信息
const claudeModels = Object.keys(jsonData).filter((k) => k.includes('claude')).length
const gptModels = Object.keys(jsonData).filter((k) => k.includes('gpt')).length
const geminiModels = Object.keys(jsonData).filter((k) => k.includes('gemini')).length
log.info('Model breakdown:')
log.info(` - Claude models: ${claudeModels}`)
log.info(` - GPT models: ${gptModels}`)
log.info(` - Gemini models: ${geminiModels}`)
log.info(` - Other models: ${modelCount - claudeModels - gptModels - geminiModels}`)
resolve(jsonData)
} catch (error) {
reject(new Error(`Failed to parse pricing data: ${error.message}`))
}
})
})
request.on('error', (error) => {
reject(new Error(`Network error: ${error.message}`))
})
request.setTimeout(config.timeout, () => {
request.destroy()
reject(new Error(`Download timeout after ${config.timeout / 1000} seconds`))
})
})
}
// 使用 fallback 文件
function useFallback() {
log.warn('Attempting to use fallback pricing data...')
if (!fs.existsSync(config.fallbackFile)) {
log.error(`Fallback file not found: ${config.fallbackFile}`)
return false
}
try {
const fallbackData = fs.readFileSync(config.fallbackFile, 'utf8')
const jsonData = JSON.parse(fallbackData)
// 保存到data目录
fs.writeFileSync(config.pricingFile, JSON.stringify(jsonData, null, 2))
const modelCount = Object.keys(jsonData).length
log.warn(`Using fallback pricing data for ${modelCount} models`)
log.info('Note: Fallback data may be outdated. Try updating again later.')
return true
} catch (error) {
log.error(`Failed to use fallback: ${error.message}`)
return false
}
}
// 显示当前状态
function showCurrentStatus() {
if (fs.existsSync(config.pricingFile)) {
const stats = fs.statSync(config.pricingFile)
const fileAge = Date.now() - stats.mtime.getTime()
const ageInHours = Math.round(fileAge / (60 * 60 * 1000))
const ageInDays = Math.floor(ageInHours / 24)
let ageString = ''
if (ageInDays > 0) {
ageString = `${ageInDays} day${ageInDays > 1 ? 's' : ''} and ${ageInHours % 24} hour${ageInHours % 24 !== 1 ? 's' : ''}`
} else {
ageString = `${ageInHours} hour${ageInHours !== 1 ? 's' : ''}`
}
log.info(`Current pricing file age: ${ageString}`)
try {
const data = JSON.parse(fs.readFileSync(config.pricingFile, 'utf8'))
log.info(`Current file contains ${Object.keys(data).length} models`)
} catch (error) {
log.warn('Current file exists but could not be parsed')
}
} else {
log.info('No existing pricing file found')
}
}
// 主函数
async function main() {
console.log(`${colors.bright}${colors.blue}======================================${colors.reset}`)
console.log(`${colors.bright} Model Pricing Update Tool${colors.reset}`)
console.log(
`${colors.bright}${colors.blue}======================================${colors.reset}\n`
)
// 显示当前状态
showCurrentStatus()
console.log('')
// 确保数据目录存在
ensureDataDir()
// 备份现有文件
const hasBackup = backupExistingFile()
try {
// 尝试下载最新数据
await downloadPricingData()
// 清理备份文件(成功下载后)
if (hasBackup && fs.existsSync(config.backupFile)) {
fs.unlinkSync(config.backupFile)
log.info('Cleaned up backup file')
}
console.log(`\n${colors.green}✅ Model pricing updated successfully!${colors.reset}`)
process.exit(0)
} catch (error) {
log.error(`Download failed: ${error.message}`)
// 尝试恢复备份
if (hasBackup) {
if (restoreBackup()) {
log.info('Original file restored')
}
}
// 尝试使用 fallback
if (useFallback()) {
console.log(
`\n${colors.yellow}⚠️ Using fallback data (update completed with warnings)${colors.reset}`
)
process.exit(0)
} else {
console.log(`\n${colors.red}❌ Failed to update model pricing${colors.reset}`)
process.exit(1)
}
}
}
// 处理未捕获的错误
process.on('unhandledRejection', (error) => {
log.error(`Unhandled error: ${error.message}`)
process.exit(1)
})
// 运行主函数
main().catch((error) => {
log.error(`Fatal error: ${error.message}`)
process.exit(1)
})
|