@adversity/coding-tool-x 3.1.1 → 3.1.3

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.
Files changed (69) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/web/assets/Analytics-BIqc8Rin.css +1 -0
  3. package/dist/web/assets/Analytics-D2V09DHH.js +39 -0
  4. package/dist/web/assets/{ConfigTemplates-ZrK_s7ma.js → ConfigTemplates-Bf_11LhH.js} +1 -1
  5. package/dist/web/assets/Home-BRnW4FTS.js +1 -0
  6. package/dist/web/assets/Home-CyCIx4BA.css +1 -0
  7. package/dist/web/assets/{PluginManager-BD7QUZbU.js → PluginManager-B9J32GhW.js} +1 -1
  8. package/dist/web/assets/{ProjectList-DRb1DuHV.js → ProjectList-5a19MWJk.js} +1 -1
  9. package/dist/web/assets/SessionList-CXUr6S7w.css +1 -0
  10. package/dist/web/assets/SessionList-Cxg5bAdT.js +1 -0
  11. package/dist/web/assets/{SkillManager-C1xG5B4Q.js → SkillManager-CVBr0CLi.js} +1 -1
  12. package/dist/web/assets/{Terminal-DksBo_lM.js → Terminal-D2Xe_Q0H.js} +1 -1
  13. package/dist/web/assets/{WorkspaceManager-Burx7XOo.js → WorkspaceManager-C7dwV94C.js} +1 -1
  14. package/dist/web/assets/icons-BxcwoY5F.js +1 -0
  15. package/dist/web/assets/index-BS9RA6SN.js +2 -0
  16. package/dist/web/assets/index-DUNAVDGb.css +1 -0
  17. package/dist/web/assets/naive-ui-BIXcURHZ.js +1 -0
  18. package/dist/web/assets/{vendors-CO3Upi1d.js → vendors-i5CBGnlm.js} +1 -1
  19. package/dist/web/assets/{vue-vendor-DqyWIXEb.js → vue-vendor-PKd8utv_.js} +1 -1
  20. package/dist/web/index.html +6 -6
  21. package/package.json +1 -1
  22. package/src/config/default.js +7 -27
  23. package/src/config/loader.js +6 -3
  24. package/src/config/model-metadata.js +167 -0
  25. package/src/config/model-metadata.json +125 -0
  26. package/src/config/model-pricing.js +23 -93
  27. package/src/server/api/channels.js +16 -39
  28. package/src/server/api/codex-channels.js +15 -43
  29. package/src/server/api/commands.js +0 -77
  30. package/src/server/api/config.js +4 -1
  31. package/src/server/api/gemini-channels.js +16 -40
  32. package/src/server/api/opencode-channels.js +108 -56
  33. package/src/server/api/opencode-proxy.js +42 -33
  34. package/src/server/api/opencode-sessions.js +4 -69
  35. package/src/server/api/sessions.js +11 -68
  36. package/src/server/api/settings.js +138 -0
  37. package/src/server/api/skills.js +0 -44
  38. package/src/server/api/statistics.js +115 -1
  39. package/src/server/codex-proxy-server.js +32 -59
  40. package/src/server/gemini-proxy-server.js +21 -18
  41. package/src/server/index.js +13 -7
  42. package/src/server/opencode-proxy-server.js +1232 -197
  43. package/src/server/proxy-server.js +8 -8
  44. package/src/server/services/codex-sessions.js +105 -6
  45. package/src/server/services/commands-service.js +0 -29
  46. package/src/server/services/config-templates-service.js +38 -28
  47. package/src/server/services/env-checker.js +97 -9
  48. package/src/server/services/env-manager.js +29 -1
  49. package/src/server/services/opencode-channels.js +3 -1
  50. package/src/server/services/opencode-sessions.js +486 -218
  51. package/src/server/services/opencode-settings-manager.js +172 -36
  52. package/src/server/services/plugins-service.js +37 -28
  53. package/src/server/services/pty-manager.js +22 -18
  54. package/src/server/services/response-decoder.js +21 -0
  55. package/src/server/services/skill-service.js +1 -49
  56. package/src/server/services/speed-test.js +40 -3
  57. package/src/server/services/statistics-service.js +238 -1
  58. package/src/server/utils/pricing.js +51 -60
  59. package/src/server/websocket-server.js +24 -5
  60. package/dist/web/assets/Home-B8YfhZ3c.js +0 -1
  61. package/dist/web/assets/Home-Di2qsylF.css +0 -1
  62. package/dist/web/assets/SessionList-BGJWyneI.css +0 -1
  63. package/dist/web/assets/SessionList-lZ0LKzfT.js +0 -1
  64. package/dist/web/assets/icons-kcfLIMBB.js +0 -1
  65. package/dist/web/assets/index-Ufv5rCa5.css +0 -1
  66. package/dist/web/assets/index-lAkrRC3h.js +0 -2
  67. package/dist/web/assets/naive-ui-CSrLusZZ.js +0 -1
  68. package/src/server/api/convert.js +0 -260
  69. package/src/server/services/session-converter.js +0 -577
@@ -7,11 +7,11 @@ const { allocateChannel, releaseChannel, getSchedulerState } = require('./servic
7
7
  const { recordSuccess, recordFailure } = require('./services/channel-health');
8
8
  const { loadConfig } = require('../config/loader');
9
9
  const DEFAULT_CONFIG = require('../config/default');
10
- const { resolvePricing } = require('./utils/pricing');
10
+ const { resolveModelPricing } = require('./utils/pricing');
11
11
  const { recordRequest: recordCodexRequest } = require('./services/codex-statistics-service');
12
12
  const { saveProxyStartTime, clearProxyStartTime, getProxyStartTime, getProxyRuntime } = require('./services/proxy-runtime');
13
+ const { createDecodedStream } = require('./services/response-decoder');
13
14
  const { getEnabledChannels, writeCodexConfigForMultiChannel, getEffectiveApiKey } = require('./services/codex-channels');
14
- const { CLAUDE_MODEL_PRICING } = require('../config/model-pricing');
15
15
 
16
16
  let proxyServer = null;
17
17
  let proxyApp = null;
@@ -25,7 +25,7 @@ const requestMetadata = new Map();
25
25
  const printedRedirectCache = new Map();
26
26
 
27
27
  // OpenAI 模型定价(每百万 tokens 的价格,单位:美元)
28
- // Claude 模型使用 config/model-pricing.js 中的集中定价
28
+ // 作为 model-metadata 未覆盖时的兜底值
29
29
  const PRICING = {
30
30
  'gpt-4o': { input: 2.5, output: 10 },
31
31
  'gpt-4o-2024-11-20': { input: 2.5, output: 10 },
@@ -142,61 +142,33 @@ function resolveCodexTarget(baseUrl = '', requestPath = '') {
142
142
  * 计算请求成本
143
143
  */
144
144
  function calculateCost(model, tokens) {
145
- let pricing;
146
-
147
- // 首先检查是否是 Claude 模型,使用集中定价
148
- if (model.startsWith('claude-') || model.toLowerCase().includes('claude')) {
149
- pricing = CLAUDE_MODEL_PRICING[model];
150
-
151
- // 如果没有精确匹配,尝试模糊匹配 Claude 模型
152
- if (!pricing) {
153
- const modelLower = model.toLowerCase();
154
- // 查找最接近的 Claude 模型
155
- for (const [key, value] of Object.entries(CLAUDE_MODEL_PRICING)) {
156
- if (key.toLowerCase().includes(modelLower) || modelLower.includes(key.toLowerCase())) {
157
- pricing = value;
158
- break;
159
- }
160
- }
161
- }
162
-
163
- // 如果仍然没有找到,使用默认 Sonnet 定价
164
- if (!pricing) {
165
- pricing = CLAUDE_MODEL_PRICING['claude-sonnet-4-5-20250929'];
166
- }
167
- } else {
168
- // 非 Claude 模型,使用 PRICING 对象(OpenAI 等)
169
- pricing = PRICING[model];
170
-
171
- // 如果没有精确匹配,尝试模糊匹配
172
- if (!pricing) {
173
- const modelLower = model.toLowerCase();
174
- if (modelLower.includes('gpt-4o-mini')) {
175
- pricing = PRICING['gpt-4o-mini'];
176
- } else if (modelLower.includes('gpt-4o')) {
177
- pricing = PRICING['gpt-4o'];
178
- } else if (modelLower.includes('gpt-4')) {
179
- pricing = PRICING['gpt-4'];
180
- } else if (modelLower.includes('gpt-3.5')) {
181
- pricing = PRICING['gpt-3.5-turbo'];
182
- } else if (modelLower.includes('o1-mini')) {
183
- pricing = PRICING['o1-mini'];
184
- } else if (modelLower.includes('o1-pro')) {
185
- pricing = PRICING['o1-pro'];
186
- } else if (modelLower.includes('o1')) {
187
- pricing = PRICING['o1'];
188
- } else if (modelLower.includes('o3-mini')) {
189
- pricing = PRICING['o3-mini'];
190
- } else if (modelLower.includes('o3')) {
191
- pricing = PRICING['o3'];
192
- } else if (modelLower.includes('o4-mini')) {
193
- pricing = PRICING['o4-mini'];
194
- }
145
+ let fallbackPricing = PRICING[model];
146
+ if (!fallbackPricing) {
147
+ const modelLower = String(model || '').toLowerCase();
148
+ if (modelLower.includes('gpt-4o-mini')) {
149
+ fallbackPricing = PRICING['gpt-4o-mini'];
150
+ } else if (modelLower.includes('gpt-4o')) {
151
+ fallbackPricing = PRICING['gpt-4o'];
152
+ } else if (modelLower.includes('gpt-4')) {
153
+ fallbackPricing = PRICING['gpt-4'];
154
+ } else if (modelLower.includes('gpt-3.5')) {
155
+ fallbackPricing = PRICING['gpt-3.5-turbo'];
156
+ } else if (modelLower.includes('o1-mini')) {
157
+ fallbackPricing = PRICING['o1-mini'];
158
+ } else if (modelLower.includes('o1-pro')) {
159
+ fallbackPricing = PRICING['o1-pro'];
160
+ } else if (modelLower.includes('o1')) {
161
+ fallbackPricing = PRICING['o1'];
162
+ } else if (modelLower.includes('o3-mini')) {
163
+ fallbackPricing = PRICING['o3-mini'];
164
+ } else if (modelLower.includes('o3')) {
165
+ fallbackPricing = PRICING['o3'];
166
+ } else if (modelLower.includes('o4-mini')) {
167
+ fallbackPricing = PRICING['o4-mini'];
195
168
  }
196
169
  }
197
170
 
198
- // 默认使用基础定价
199
- pricing = resolvePricing('codex', pricing, CODEX_BASE_PRICING);
171
+ const pricing = resolveModelPricing('codex', model, fallbackPricing, CODEX_BASE_PRICING);
200
172
  const inputRate = typeof pricing.input === 'number' ? pricing.input : CODEX_BASE_PRICING.input;
201
173
  const outputRate = typeof pricing.output === 'number' ? pricing.output : CODEX_BASE_PRICING.output;
202
174
 
@@ -402,14 +374,15 @@ async function startCodexProxyServer(options = {}) {
402
374
  totalTokens: 0,
403
375
  model: ''
404
376
  };
377
+ const parsedStream = createDecodedStream(proxyRes);
405
378
 
406
- proxyRes.on('data', (chunk) => {
379
+ parsedStream.on('data', (chunk) => {
407
380
  // 如果响应已关闭,停止处理
408
381
  if (isResponseClosed) {
409
382
  return;
410
383
  }
411
384
 
412
- buffer += chunk.toString();
385
+ buffer += chunk.toString('utf8');
413
386
 
414
387
  // 检查是否是 SSE 流
415
388
  if (proxyRes.headers['content-type']?.includes('text/event-stream')) {
@@ -475,7 +448,7 @@ async function startCodexProxyServer(options = {}) {
475
448
  }
476
449
  });
477
450
 
478
- proxyRes.on('end', () => {
451
+ parsedStream.on('end', () => {
479
452
  // 如果不是流式响应,尝试从完整响应中解析
480
453
  if (!proxyRes.headers['content-type']?.includes('text/event-stream')) {
481
454
  try {
@@ -558,7 +531,7 @@ async function startCodexProxyServer(options = {}) {
558
531
  }
559
532
  });
560
533
 
561
- proxyRes.on('error', (err) => {
534
+ parsedStream.on('error', (err) => {
562
535
  // 忽略代理响应错误(可能是网络问题)
563
536
  if (err.code !== 'EPIPE' && err.code !== 'ECONNRESET') {
564
537
  console.error('Proxy response error:', err);
@@ -7,9 +7,10 @@ const { allocateChannel, releaseChannel, getSchedulerState } = require('./servic
7
7
  const { recordSuccess, recordFailure } = require('./services/channel-health');
8
8
  const { loadConfig } = require('../config/loader');
9
9
  const DEFAULT_CONFIG = require('../config/default');
10
- const { resolvePricing } = require('./utils/pricing');
10
+ const { resolveModelPricing } = require('./utils/pricing');
11
11
  const { recordRequest: recordGeminiRequest } = require('./services/gemini-statistics-service');
12
12
  const { saveProxyStartTime, clearProxyStartTime, getProxyStartTime, getProxyRuntime } = require('./services/proxy-runtime');
13
+ const { createDecodedStream } = require('./services/response-decoder');
13
14
  const { getEffectiveApiKey } = require('./services/gemini-channels');
14
15
 
15
16
  let proxyServer = null;
@@ -24,6 +25,7 @@ const requestMetadata = new Map();
24
25
  const printedGeminiRedirectCache = new Map();
25
26
 
26
27
  // Gemini 模型定价(每百万 tokens 的价格,单位:美元)
28
+ // 作为 model-metadata 未覆盖时的兜底值
27
29
  const PRICING = {
28
30
  'gemini-2.5-pro': { input: 1.25, output: 5 },
29
31
  'gemini-2.5-flash': { input: 0.075, output: 0.3 },
@@ -78,33 +80,33 @@ function redirectModel(originalModel, channel) {
78
80
  */
79
81
  function calculateCost(model, tokens) {
80
82
  // 尝试精确匹配
81
- let pricing = PRICING[model];
83
+ let fallbackPricing = PRICING[model];
82
84
 
83
85
  // 如果没有精确匹配,尝试模糊匹配
84
- if (!pricing) {
85
- const modelLower = model.toLowerCase();
86
+ if (!fallbackPricing) {
87
+ const modelLower = String(model || '').toLowerCase();
86
88
  if (modelLower.includes('gemini-2.5-pro')) {
87
- pricing = PRICING['gemini-2.5-pro'];
89
+ fallbackPricing = PRICING['gemini-2.5-pro'];
88
90
  } else if (modelLower.includes('gemini-2.5-flash')) {
89
- pricing = PRICING['gemini-2.5-flash'];
91
+ fallbackPricing = PRICING['gemini-2.5-flash'];
90
92
  } else if (modelLower.includes('gemini-2.0-flash-thinking')) {
91
- pricing = PRICING['gemini-2.0-flash-thinking-exp-1219'];
93
+ fallbackPricing = PRICING['gemini-2.0-flash-thinking-exp-1219'];
92
94
  } else if (modelLower.includes('gemini-2.0-flash')) {
93
- pricing = PRICING['gemini-2.0-flash-exp'];
95
+ fallbackPricing = PRICING['gemini-2.0-flash-exp'];
94
96
  } else if (modelLower.includes('gemini-1.5-pro')) {
95
- pricing = PRICING['gemini-1.5-pro'];
97
+ fallbackPricing = PRICING['gemini-1.5-pro'];
96
98
  } else if (modelLower.includes('gemini-1.5-flash-8b')) {
97
- pricing = PRICING['gemini-1.5-flash-8b'];
99
+ fallbackPricing = PRICING['gemini-1.5-flash-8b'];
98
100
  } else if (modelLower.includes('gemini-1.5-flash')) {
99
- pricing = PRICING['gemini-1.5-flash'];
101
+ fallbackPricing = PRICING['gemini-1.5-flash'];
100
102
  } else if (modelLower.includes('gemini-1.0-pro')) {
101
- pricing = PRICING['gemini-1.0-pro'];
103
+ fallbackPricing = PRICING['gemini-1.0-pro'];
102
104
  } else if (modelLower.includes('gemini-pro')) {
103
- pricing = PRICING['gemini-pro'];
105
+ fallbackPricing = PRICING['gemini-pro'];
104
106
  }
105
107
  }
106
108
 
107
- pricing = resolvePricing('gemini', pricing, GEMINI_BASE_PRICING);
109
+ const pricing = resolveModelPricing('gemini', model, fallbackPricing, GEMINI_BASE_PRICING);
108
110
  const inputRate = typeof pricing.input === 'number' ? pricing.input : GEMINI_BASE_PRICING.input;
109
111
  const outputRate = typeof pricing.output === 'number' ? pricing.output : GEMINI_BASE_PRICING.output;
110
112
 
@@ -289,14 +291,15 @@ async function startGeminiProxyServer(options = {}) {
289
291
  totalTokens: 0,
290
292
  model: ''
291
293
  };
294
+ const parsedStream = createDecodedStream(proxyRes);
292
295
 
293
- proxyRes.on('data', (chunk) => {
296
+ parsedStream.on('data', (chunk) => {
294
297
  // 如果响应已关闭,停止处理
295
298
  if (isResponseClosed) {
296
299
  return;
297
300
  }
298
301
 
299
- buffer += chunk.toString();
302
+ buffer += chunk.toString('utf8');
300
303
 
301
304
  // 检查是否是 SSE 流
302
305
  if (proxyRes.headers['content-type']?.includes('text/event-stream')) {
@@ -360,7 +363,7 @@ async function startGeminiProxyServer(options = {}) {
360
363
  }
361
364
  });
362
365
 
363
- proxyRes.on('end', () => {
366
+ parsedStream.on('end', () => {
364
367
  // 如果不是流式响应,尝试从完整响应中解析
365
368
  if (!proxyRes.headers['content-type']?.includes('text/event-stream')) {
366
369
  try {
@@ -467,7 +470,7 @@ async function startGeminiProxyServer(options = {}) {
467
470
  }
468
471
  });
469
472
 
470
- proxyRes.on('error', (err) => {
473
+ parsedStream.on('error', (err) => {
471
474
  // 忽略代理响应错误(可能是网络问题)
472
475
  if (err.code !== 'EPIPE' && err.code !== 'ECONNRESET') {
473
476
  console.error('Proxy response error:', err);
@@ -15,7 +15,7 @@ const { setProxyConfig: setOpenCodeProxyConfig } = require('./services/opencode-
15
15
  const { startProxyServer } = require('./proxy-server');
16
16
  const { startCodexProxyServer } = require('./codex-proxy-server');
17
17
  const { startGeminiProxyServer } = require('./gemini-proxy-server');
18
- const { startOpenCodeProxyServer } = require('./opencode-proxy-server');
18
+ const { startOpenCodeProxyServer, collectProxyModelList } = require('./opencode-proxy-server');
19
19
  const { createRemoteMutationGuard, createRemoteRouteGuard } = require('./services/network-access');
20
20
 
21
21
  function isInteractivePortConflictMode(options = {}) {
@@ -158,9 +158,6 @@ async function startServer(port, host = '127.0.0.1', options = {}) {
158
158
  app.use('/api/opencode/proxy', require('./api/opencode-proxy'));
159
159
  app.use('/api/opencode/statistics', require('./api/opencode-statistics'));
160
160
 
161
- // 会话格式转换 API
162
- app.use('/api/convert', require('./api/convert'));
163
-
164
161
  app.use('/api/aliases', require('./api/aliases')());
165
162
  app.use('/api/favorites', require('./api/favorites'));
166
163
  app.use('/api/ui-config', require('./api/ui-config'));
@@ -269,8 +266,8 @@ async function startServer(port, host = '127.0.0.1', options = {}) {
269
266
  // 自动恢复代理状态
270
267
  autoRestoreProxies();
271
268
 
272
- // 启动时执行健康检查
273
- performStartupHealthCheck();
269
+ // 延迟执行健康检查,避免阻塞启动
270
+ setTimeout(() => performStartupHealthCheck(), 2000);
274
271
 
275
272
  return server;
276
273
  }
@@ -349,7 +346,7 @@ function autoRestoreProxies() {
349
346
  console.log(chalk.cyan('\n🔄 检测到 OpenCode 代理状态文件,正在自动启动...'));
350
347
  const opencodeProxyPort = config.ports?.opencodeProxy || 20091;
351
348
  startOpenCodeProxyServer(opencodeProxyPort)
352
- .then((result) => {
349
+ .then(async (result) => {
353
350
  if (result.success) {
354
351
  console.log(chalk.green(`✅ OpenCode 代理已自动启动,端口: ${result.port}`));
355
352
  try {
@@ -365,6 +362,15 @@ function autoRestoreProxies() {
365
362
  }
366
363
  });
367
364
  });
365
+ const detectedModels = await collectProxyModelList(enabledChs, { useCacheOnly: true });
366
+ if (Array.isArray(detectedModels)) {
367
+ detectedModels.forEach((m) => {
368
+ if (typeof m === 'string' && m.trim() && !seen.has(m.trim().toLowerCase())) {
369
+ seen.add(m.trim().toLowerCase());
370
+ allModels.push(m.trim());
371
+ }
372
+ });
373
+ }
368
374
  const firstChannel = enabledChs[0];
369
375
  const activeModel = firstChannel && (firstChannel.model || firstChannel.speedTestModel) || null;
370
376
  const cfgResult = setOpenCodeProxyConfig(result.port, { model: activeModel, models: allModels });