@adversity/coding-tool-x 3.0.3 → 3.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -0
- package/dist/web/assets/{index-TjhcaFRe.css → index-19ZPjh5b.css} +2 -2
- package/dist/web/assets/{index-BDN4_LfP.js → index-B4w1yh7H.js} +2 -2
- package/dist/web/index.html +2 -2
- package/docs/model-redirection.md +251 -0
- package/package.json +1 -1
- package/src/config/default.js +2 -9
- package/src/config/model-pricing.js +105 -0
- package/src/server/api/workspaces.js +5 -3
- package/src/server/codex-proxy-server.js +153 -36
- package/src/server/proxy-server.js +74 -13
- package/src/server/services/channel-scheduler.js +3 -1
- package/src/server/services/channels.js +4 -1
- package/src/server/services/mcp-client.js +17 -2
- package/src/server/services/model-detector.js +6 -4
- package/src/server/services/workspace-service.js +126 -5
- package/src/server/utils/pricing.js +13 -3
|
@@ -12,6 +12,7 @@ const { resolvePricing, resolveModelPricing } = require('./utils/pricing');
|
|
|
12
12
|
const { recordRequest } = require('./services/statistics-service');
|
|
13
13
|
const { saveProxyStartTime, clearProxyStartTime, getProxyStartTime, getProxyRuntime } = require('./services/proxy-runtime');
|
|
14
14
|
const eventBus = require('../plugins/event-bus');
|
|
15
|
+
const { CLAUDE_MODEL_PRICING } = require('../config/model-pricing');
|
|
15
16
|
|
|
16
17
|
let proxyServer = null;
|
|
17
18
|
let proxyApp = null;
|
|
@@ -20,21 +21,67 @@ let currentPort = null;
|
|
|
20
21
|
// 用于存储每个请求的元数据(用于 WebSocket 日志)
|
|
21
22
|
const requestMetadata = new Map();
|
|
22
23
|
|
|
23
|
-
// Claude API 定价(每百万 tokens 的价格,单位:美元)
|
|
24
|
-
const PRICING = {
|
|
25
|
-
'claude-sonnet-4-5-20250929': { input: 3, output: 15, cacheCreation: 3.75, cacheRead: 0.30 },
|
|
26
|
-
'claude-sonnet-4-20250514': { input: 3, output: 15, cacheCreation: 3.75, cacheRead: 0.30 },
|
|
27
|
-
'claude-sonnet-3-5-20241022': { input: 3, output: 15, cacheCreation: 3.75, cacheRead: 0.30 },
|
|
28
|
-
'claude-sonnet-3-5-20240620': { input: 3, output: 15, cacheCreation: 3.75, cacheRead: 0.30 },
|
|
29
|
-
'claude-opus-4-20250514': { input: 15, output: 75, cacheCreation: 18.75, cacheRead: 1.50 },
|
|
30
|
-
'claude-opus-3-20240229': { input: 15, output: 75, cacheCreation: 18.75, cacheRead: 1.50 },
|
|
31
|
-
'claude-haiku-3-5-20241022': { input: 0.8, output: 4, cacheCreation: 1, cacheRead: 0.08 },
|
|
32
|
-
'claude-3-5-haiku-20241022': { input: 0.8, output: 4, cacheCreation: 1, cacheRead: 0.08 }
|
|
33
|
-
};
|
|
34
|
-
|
|
35
24
|
const CLAUDE_BASE_PRICING = DEFAULT_CONFIG.pricing.claude;
|
|
36
25
|
const ONE_MILLION = 1000000;
|
|
37
26
|
|
|
27
|
+
/**
|
|
28
|
+
* 检测模型层级
|
|
29
|
+
* @param {string} modelName - 模型名称
|
|
30
|
+
* @returns {string|null} 模型层级 (opus/sonnet/haiku) 或 null
|
|
31
|
+
*/
|
|
32
|
+
function detectModelTier(modelName) {
|
|
33
|
+
if (!modelName) return null;
|
|
34
|
+
const lower = modelName.toLowerCase();
|
|
35
|
+
if (lower.includes('opus')) return 'opus';
|
|
36
|
+
if (lower.includes('sonnet')) return 'sonnet';
|
|
37
|
+
if (lower.includes('haiku')) return 'haiku';
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 应用模型重定向
|
|
43
|
+
* @param {string} originalModel - 原始模型名称
|
|
44
|
+
* @param {object} channel - 渠道对象,包含 modelConfig 和 modelRedirects
|
|
45
|
+
* @returns {string} 重定向后的模型名称
|
|
46
|
+
*/
|
|
47
|
+
function redirectModel(originalModel, channel) {
|
|
48
|
+
if (!originalModel) return originalModel;
|
|
49
|
+
|
|
50
|
+
// 优先使用新的 modelRedirects 数组格式
|
|
51
|
+
const modelRedirects = channel?.modelRedirects;
|
|
52
|
+
if (Array.isArray(modelRedirects) && modelRedirects.length > 0) {
|
|
53
|
+
for (const rule of modelRedirects) {
|
|
54
|
+
if (rule.from && rule.to && rule.from === originalModel) {
|
|
55
|
+
return rule.to;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 向后兼容:使用旧的 modelConfig 格式
|
|
61
|
+
const modelConfig = channel?.modelConfig;
|
|
62
|
+
if (!modelConfig) return originalModel;
|
|
63
|
+
|
|
64
|
+
const tier = detectModelTier(originalModel);
|
|
65
|
+
|
|
66
|
+
// 优先级:层级特定配置 > 通用模型覆盖
|
|
67
|
+
if (tier === 'opus' && modelConfig.opusModel) {
|
|
68
|
+
return modelConfig.opusModel;
|
|
69
|
+
}
|
|
70
|
+
if (tier === 'sonnet' && modelConfig.sonnetModel) {
|
|
71
|
+
return modelConfig.sonnetModel;
|
|
72
|
+
}
|
|
73
|
+
if (tier === 'haiku' && modelConfig.haikuModel) {
|
|
74
|
+
return modelConfig.haikuModel;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 回退到通用模型覆盖
|
|
78
|
+
if (modelConfig.model) {
|
|
79
|
+
return modelConfig.model;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return originalModel;
|
|
83
|
+
}
|
|
84
|
+
|
|
38
85
|
/**
|
|
39
86
|
* 计算请求成本
|
|
40
87
|
* @param {string} model - 模型名称
|
|
@@ -42,7 +89,7 @@ const ONE_MILLION = 1000000;
|
|
|
42
89
|
* @returns {number} 成本(美元)
|
|
43
90
|
*/
|
|
44
91
|
function calculateCost(model, tokens) {
|
|
45
|
-
const hardcodedPricing =
|
|
92
|
+
const hardcodedPricing = CLAUDE_MODEL_PRICING[model] || {};
|
|
46
93
|
const pricing = resolveModelPricing('claude', model, hardcodedPricing, CLAUDE_BASE_PRICING);
|
|
47
94
|
|
|
48
95
|
const inputRate = typeof pricing.input === 'number' ? pricing.input : CLAUDE_BASE_PRICING.input;
|
|
@@ -168,6 +215,20 @@ async function startProxyServer(options = {}) {
|
|
|
168
215
|
|
|
169
216
|
req.selectedChannel = channel;
|
|
170
217
|
req.sessionId = sessionId || null;
|
|
218
|
+
|
|
219
|
+
// 应用模型重定向(当 proxy 开启时)
|
|
220
|
+
if (req.body && req.body.model) {
|
|
221
|
+
const originalModel = req.body.model;
|
|
222
|
+
const redirectedModel = redirectModel(originalModel, channel);
|
|
223
|
+
|
|
224
|
+
if (redirectedModel !== originalModel) {
|
|
225
|
+
req.body.model = redirectedModel;
|
|
226
|
+
// 更新 rawBody 以匹配修改后的 body
|
|
227
|
+
req.rawBody = Buffer.from(JSON.stringify(req.body));
|
|
228
|
+
console.log(`[Model Redirect] ${originalModel} → ${redirectedModel} (channel: ${channel.name})`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
171
232
|
let released = false;
|
|
172
233
|
|
|
173
234
|
const release = () => {
|
|
@@ -74,7 +74,9 @@ function refreshChannels(source = 'claude') {
|
|
|
74
74
|
baseUrl: ch.baseUrl,
|
|
75
75
|
apiKey: ch.apiKey,
|
|
76
76
|
weight: Math.max(1, Number(ch.weight) || 1),
|
|
77
|
-
maxConcurrency: ch.maxConcurrency ?? null
|
|
77
|
+
maxConcurrency: ch.maxConcurrency ?? null,
|
|
78
|
+
modelConfig: ch.modelConfig || null,
|
|
79
|
+
modelRedirects: ch.modelRedirects || []
|
|
78
80
|
}));
|
|
79
81
|
|
|
80
82
|
state.channels.forEach(ch => {
|
|
@@ -187,6 +187,7 @@ function createChannel(name, baseUrl, apiKey, websiteUrl, extraConfig = {}) {
|
|
|
187
187
|
maxConcurrency: extraConfig.maxConcurrency,
|
|
188
188
|
presetId: extraConfig.presetId || 'official',
|
|
189
189
|
modelConfig: extraConfig.modelConfig || null,
|
|
190
|
+
modelRedirects: extraConfig.modelRedirects || [],
|
|
190
191
|
proxyUrl: extraConfig.proxyUrl || '',
|
|
191
192
|
speedTestModel: extraConfig.speedTestModel || null
|
|
192
193
|
});
|
|
@@ -215,8 +216,10 @@ function updateChannel(id, updates) {
|
|
|
215
216
|
enabled: merged.enabled,
|
|
216
217
|
presetId: merged.presetId,
|
|
217
218
|
modelConfig: merged.modelConfig,
|
|
219
|
+
modelRedirects: merged.modelRedirects || [],
|
|
218
220
|
proxyUrl: merged.proxyUrl,
|
|
219
|
-
speedTestModel: merged.speedTestModel
|
|
221
|
+
speedTestModel: merged.speedTestModel,
|
|
222
|
+
updatedAt: Date.now()
|
|
220
223
|
});
|
|
221
224
|
|
|
222
225
|
// Get proxy status
|
|
@@ -238,8 +238,15 @@ class McpClient extends EventEmitter {
|
|
|
238
238
|
}, this._timeout);
|
|
239
239
|
|
|
240
240
|
try {
|
|
241
|
+
// 确保 PATH 不被覆盖,优先使用用户提供的 env,但保留 PATH
|
|
242
|
+
const mergedEnv = { ...process.env, ...env };
|
|
243
|
+
// 如果用户提供的 env 中有 PATH,将其追加到系统 PATH 前面
|
|
244
|
+
if (env && env.PATH && process.env.PATH) {
|
|
245
|
+
mergedEnv.PATH = `${env.PATH}:${process.env.PATH}`;
|
|
246
|
+
}
|
|
247
|
+
|
|
241
248
|
this._child = spawn(command, args, {
|
|
242
|
-
env:
|
|
249
|
+
env: mergedEnv,
|
|
243
250
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
244
251
|
cwd: cwd || process.cwd()
|
|
245
252
|
});
|
|
@@ -267,7 +274,15 @@ class McpClient extends EventEmitter {
|
|
|
267
274
|
|
|
268
275
|
this._child.on('error', (err) => {
|
|
269
276
|
if (err.code === 'ENOENT') {
|
|
270
|
-
|
|
277
|
+
const pathHint = mergedEnv.PATH
|
|
278
|
+
? `\n Current PATH: ${mergedEnv.PATH.split(':').slice(0, 5).join(':')}\n (showing first 5 entries)`
|
|
279
|
+
: '\n PATH is not set!';
|
|
280
|
+
settle(new McpClientError(
|
|
281
|
+
`Command "${command}" not found. Please check:\n` +
|
|
282
|
+
` 1. Is "${command}" installed?\n` +
|
|
283
|
+
` 2. Try using absolute path (e.g., /usr/bin/node or $(which ${command}))\n` +
|
|
284
|
+
` 3. Check your PATH environment variable${pathHint}`
|
|
285
|
+
));
|
|
271
286
|
} else {
|
|
272
287
|
settle(new McpClientError(`Failed to start process: ${err.message}`));
|
|
273
288
|
}
|
|
@@ -13,11 +13,13 @@ const { URL } = require('url');
|
|
|
13
13
|
// Model priority by channel type
|
|
14
14
|
const MODEL_PRIORITY = {
|
|
15
15
|
claude: [
|
|
16
|
-
'claude-
|
|
17
|
-
'claude-3-5-haiku-20241022',
|
|
18
|
-
'claude-sonnet-4-20250514',
|
|
16
|
+
'claude-opus-4-5-20250929',
|
|
19
17
|
'claude-sonnet-4-5-20250929',
|
|
20
|
-
'claude-
|
|
18
|
+
'claude-haiku-4-5-20250929',
|
|
19
|
+
'claude-sonnet-4-20250514',
|
|
20
|
+
'claude-opus-4-20250514',
|
|
21
|
+
'claude-haiku-3-5-20241022',
|
|
22
|
+
'claude-3-5-haiku-20241022'
|
|
21
23
|
],
|
|
22
24
|
codex: ['gpt-4o-mini', 'gpt-4o', 'gpt-5-codex', 'o3'],
|
|
23
25
|
gemini: ['gemini-2.5-flash', 'gemini-2.5-pro']
|
|
@@ -183,10 +183,80 @@ function createWorkspace(options) {
|
|
|
183
183
|
|
|
184
184
|
const workspaceProjects = [];
|
|
185
185
|
|
|
186
|
+
/**
|
|
187
|
+
* 验证 worktree 分支冲突
|
|
188
|
+
* @param {Array} projects - 项目列表
|
|
189
|
+
* @returns {Array} 冲突列表 [{repo, branch, projects: [index1, index2]}]
|
|
190
|
+
*/
|
|
191
|
+
function validateWorktreeBranches(projects) {
|
|
192
|
+
const repoMap = new Map();
|
|
193
|
+
const conflicts = [];
|
|
194
|
+
|
|
195
|
+
for (let i = 0; i < projects.length; i++) {
|
|
196
|
+
const proj = projects[i];
|
|
197
|
+
const { sourcePath, branch, createWorktree } = proj;
|
|
198
|
+
|
|
199
|
+
// Skip non-worktree projects
|
|
200
|
+
const isGit = isGitRepo(sourcePath);
|
|
201
|
+
const useWorktree = createWorktree !== undefined ? createWorktree : isGit;
|
|
202
|
+
if (!useWorktree || !isGit) continue;
|
|
203
|
+
|
|
204
|
+
// Resolve to absolute path to handle symlinks
|
|
205
|
+
const resolvedPath = fs.realpathSync(sourcePath);
|
|
206
|
+
|
|
207
|
+
// Determine target branch
|
|
208
|
+
let targetBranch = branch;
|
|
209
|
+
if (!targetBranch) {
|
|
210
|
+
try {
|
|
211
|
+
targetBranch = execSync('git rev-parse --abbrev-ref HEAD', {
|
|
212
|
+
cwd: sourcePath,
|
|
213
|
+
encoding: 'utf8'
|
|
214
|
+
}).trim();
|
|
215
|
+
} catch (e) {
|
|
216
|
+
targetBranch = 'main';
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Check for conflicts
|
|
221
|
+
if (!repoMap.has(resolvedPath)) {
|
|
222
|
+
repoMap.set(resolvedPath, new Map());
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const branches = repoMap.get(resolvedPath);
|
|
226
|
+
if (branches.has(targetBranch)) {
|
|
227
|
+
const conflictingIndex = branches.get(targetBranch);
|
|
228
|
+
conflicts.push({
|
|
229
|
+
repo: resolvedPath,
|
|
230
|
+
branch: targetBranch,
|
|
231
|
+
projects: [conflictingIndex, i]
|
|
232
|
+
});
|
|
233
|
+
} else {
|
|
234
|
+
branches.set(targetBranch, i);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return conflicts;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Validate branch uniqueness for worktree mode
|
|
242
|
+
const branchConflicts = validateWorktreeBranches(projects);
|
|
243
|
+
if (branchConflicts.length > 0) {
|
|
244
|
+
const conflict = branchConflicts[0];
|
|
245
|
+
const projectNames = conflict.projects.map(i => projects[i].name || `项目${i + 1}`).join(', ');
|
|
246
|
+
throw new Error(
|
|
247
|
+
`无法创建工作区:分支 '${conflict.branch}' 在同一仓库中被多个项目使用 (${projectNames})。\n` +
|
|
248
|
+
`仓库路径: ${conflict.repo}\n\n` +
|
|
249
|
+
`Git worktree 不允许在同一仓库的多个工作树中检出相同的分支。\n\n` +
|
|
250
|
+
`解决方案:\n` +
|
|
251
|
+
`1. 为不同项目指定不同的分支名\n` +
|
|
252
|
+
`2. 或者禁用其中一个项目的 worktree 模式(设置 createWorktree: false)`
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
186
256
|
try {
|
|
187
257
|
// 处理每个项目
|
|
188
258
|
for (const proj of projects) {
|
|
189
|
-
const { sourcePath, name: linkName, branch } = proj;
|
|
259
|
+
const { sourcePath, name: linkName, branch, baseBranch } = proj;
|
|
190
260
|
// useWorktree: 未指定时,Git 仓库默认 true,非 Git 仓库默认 false
|
|
191
261
|
const isGit = isGitRepo(sourcePath);
|
|
192
262
|
const useWorktree = proj.createWorktree !== undefined ? proj.createWorktree : isGit;
|
|
@@ -248,7 +318,15 @@ function createWorkspace(options) {
|
|
|
248
318
|
} catch (error) {
|
|
249
319
|
// 如果分支不存在,尝试创建新分支
|
|
250
320
|
try {
|
|
251
|
-
|
|
321
|
+
// 构建 git worktree add 命令
|
|
322
|
+
let worktreeCmd = `git worktree add "${worktreePath}" -b "${targetBranch}"`;
|
|
323
|
+
|
|
324
|
+
// 如果指定了基础分支,添加到命令中
|
|
325
|
+
if (baseBranch && baseBranch.trim()) {
|
|
326
|
+
worktreeCmd += ` "${baseBranch}"`;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
execSync(worktreeCmd, {
|
|
252
330
|
cwd: sourcePath,
|
|
253
331
|
stdio: 'pipe'
|
|
254
332
|
});
|
|
@@ -258,7 +336,16 @@ function createWorkspace(options) {
|
|
|
258
336
|
path: worktreePath
|
|
259
337
|
});
|
|
260
338
|
} catch (err) {
|
|
261
|
-
//
|
|
339
|
+
// Check if it's a "branch already checked out" error
|
|
340
|
+
if (err.message.includes('already checked out')) {
|
|
341
|
+
throw new Error(
|
|
342
|
+
`无法创建 worktree:分支 '${targetBranch}' 已在其他工作树中检出。\n` +
|
|
343
|
+
`错误详情: ${err.message}\n\n` +
|
|
344
|
+
`提示:请为此项目指定不同的分支名,或禁用 worktree 模式。`
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// For other errors, provide clear message but allow fallback
|
|
262
349
|
console.warn(`创建 worktree 失败,使用软链接: ${err.message}`);
|
|
263
350
|
targetPath = sourcePath;
|
|
264
351
|
worktrees = getGitWorktrees(sourcePath);
|
|
@@ -472,7 +559,7 @@ function addProjectToWorkspace(workspaceId, projectConfig) {
|
|
|
472
559
|
throw new Error('工作区不存在');
|
|
473
560
|
}
|
|
474
561
|
|
|
475
|
-
const { sourcePath, name: linkName, branch } = projectConfig;
|
|
562
|
+
const { sourcePath, name: linkName, branch, baseBranch } = projectConfig;
|
|
476
563
|
// useWorktree: 未指定时,Git 仓库默认 true,非 Git 仓库默认 false
|
|
477
564
|
const isGit = isGitRepo(sourcePath);
|
|
478
565
|
const useWorktree = projectConfig.createWorktree !== undefined ? projectConfig.createWorktree : isGit;
|
|
@@ -525,14 +612,48 @@ function addProjectToWorkspace(workspaceId, projectConfig) {
|
|
|
525
612
|
targetPath = worktreePath;
|
|
526
613
|
worktrees.push({ branch: targetBranch, path: worktreePath });
|
|
527
614
|
} catch (error) {
|
|
615
|
+
// Check if branch is already checked out elsewhere
|
|
616
|
+
if (error.message && error.message.includes('already checked out')) {
|
|
617
|
+
throw new Error(
|
|
618
|
+
`无法添加项目:分支 '${targetBranch}' 已在其他工作树中检出。\n` +
|
|
619
|
+
`仓库路径: ${sourcePath}\n\n` +
|
|
620
|
+
`Git worktree 不允许在同一仓库的多个工作树中检出相同的分支。\n\n` +
|
|
621
|
+
`解决方案:\n` +
|
|
622
|
+
`1. 指定不同的分支名\n` +
|
|
623
|
+
`2. 或者禁用 worktree 模式(设置 createWorktree: false)`
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// Branch doesn't exist, try creating it
|
|
528
628
|
try {
|
|
529
|
-
|
|
629
|
+
// 构建 git worktree add 命令
|
|
630
|
+
let worktreeCmd = `git worktree add "${worktreePath}" -b "${targetBranch}"`;
|
|
631
|
+
|
|
632
|
+
// 如果指定了基础分支,添加到命令中
|
|
633
|
+
if (baseBranch && baseBranch.trim()) {
|
|
634
|
+
worktreeCmd += ` "${baseBranch}"`;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
execSync(worktreeCmd, {
|
|
530
638
|
cwd: sourcePath,
|
|
531
639
|
stdio: 'pipe'
|
|
532
640
|
});
|
|
533
641
|
targetPath = worktreePath;
|
|
534
642
|
worktrees.push({ branch: targetBranch, path: worktreePath });
|
|
535
643
|
} catch (err) {
|
|
644
|
+
// Check for "already checked out" error in create branch attempt
|
|
645
|
+
if (err.message && err.message.includes('already checked out')) {
|
|
646
|
+
throw new Error(
|
|
647
|
+
`无法添加项目:分支 '${targetBranch}' 已在其他工作树中检出。\n` +
|
|
648
|
+
`仓库路径: ${sourcePath}\n\n` +
|
|
649
|
+
`Git worktree 不允许在同一仓库的多个工作树中检出相同的分支。\n\n` +
|
|
650
|
+
`解决方案:\n` +
|
|
651
|
+
`1. 指定不同的分支名\n` +
|
|
652
|
+
`2. 或者禁用 worktree 模式(设置 createWorktree: false)`
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Other errors: fall back to symlink mode
|
|
536
657
|
console.warn(`创建 worktree 失败,使用软链接: ${err.message}`);
|
|
537
658
|
targetPath = sourcePath;
|
|
538
659
|
worktrees = getGitWorktrees(sourcePath);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const { loadConfig } = require('../../config/loader');
|
|
2
2
|
const DEFAULT_CONFIG = require('../../config/default');
|
|
3
|
+
const { CLAUDE_MODEL_PRICING, CLAUDE_MODEL_ALIASES } = require('../../config/model-pricing');
|
|
3
4
|
|
|
4
5
|
const RATE_KEYS = ['input', 'output', 'cacheCreation', 'cacheRead', 'cached', 'reasoning'];
|
|
5
6
|
|
|
@@ -39,7 +40,7 @@ function resolvePricing(toolKey, modelPricing = {}, defaultPricing = {}) {
|
|
|
39
40
|
function resolveModelPricing(toolKey, model, hardcodedPricing = {}, defaultPricing = {}) {
|
|
40
41
|
const config = getPricingConfig(toolKey);
|
|
41
42
|
|
|
42
|
-
// 1. Check
|
|
43
|
+
// 1. Check user custom config for specific model first
|
|
43
44
|
const modelConfig = config?.models?.[model];
|
|
44
45
|
if (modelConfig && modelConfig.mode === 'custom') {
|
|
45
46
|
const result = { ...hardcodedPricing };
|
|
@@ -51,7 +52,7 @@ function resolveModelPricing(toolKey, model, hardcodedPricing = {}, defaultPrici
|
|
|
51
52
|
return result;
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
// 2.
|
|
55
|
+
// 2. Check user custom config for tool-level
|
|
55
56
|
if (config && config.mode === 'custom') {
|
|
56
57
|
const result = { ...hardcodedPricing };
|
|
57
58
|
RATE_KEYS.forEach((key) => {
|
|
@@ -62,7 +63,16 @@ function resolveModelPricing(toolKey, model, hardcodedPricing = {}, defaultPrici
|
|
|
62
63
|
return result;
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
// 3. Use hardcoded pricing
|
|
66
|
+
// 3. Use centralized hardcoded pricing for known models (mode: 'auto')
|
|
67
|
+
// Normalize model name using aliases
|
|
68
|
+
const normalizedModel = CLAUDE_MODEL_ALIASES[model] || model;
|
|
69
|
+
const centralizedPricing = CLAUDE_MODEL_PRICING[normalizedModel];
|
|
70
|
+
|
|
71
|
+
if (centralizedPricing) {
|
|
72
|
+
return { ...defaultPricing, ...centralizedPricing };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 4. Fall back to base pricing for unknown models
|
|
66
76
|
return { ...defaultPricing, ...hardcodedPricing };
|
|
67
77
|
}
|
|
68
78
|
|