coding-tool-x 3.3.7 → 3.3.9

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 (89) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +253 -326
  3. package/dist/web/assets/{Analytics-IW6eAy9u.js → Analytics-D6LzK9hk.js} +1 -1
  4. package/dist/web/assets/{ConfigTemplates-BPtkTMSc.js → ConfigTemplates-BUDYuxRi.js} +1 -1
  5. package/dist/web/assets/Home-BQxQ1LhR.css +1 -0
  6. package/dist/web/assets/Home-D7KX7iF8.js +1 -0
  7. package/dist/web/assets/{PluginManager-BGx9MSDV.js → PluginManager-DTgQ--vB.js} +1 -1
  8. package/dist/web/assets/{ProjectList-BCn-mrCx.js → ProjectList-DMCiGmCT.js} +1 -1
  9. package/dist/web/assets/{SessionList-CzLfebJQ.js → SessionList-CRBsdVRe.js} +1 -1
  10. package/dist/web/assets/{SkillManager-CXz2vBQx.js → SkillManager-DMwx2Q4k.js} +1 -1
  11. package/dist/web/assets/{WorkspaceManager-CHtgMfKc.js → WorkspaceManager-DapB4ljL.js} +1 -1
  12. package/dist/web/assets/{icons-B29onFfZ.js → icons-B5Pl4lrD.js} +1 -1
  13. package/dist/web/assets/index-CL-qpoJ_.js +2 -0
  14. package/dist/web/assets/index-D_5dRFOL.css +1 -0
  15. package/dist/web/assets/{markdown-C9MYpaSi.js → markdown-DyTJGI4N.js} +1 -1
  16. package/dist/web/assets/{naive-ui-CxpuzdjU.js → naive-ui-Bdxp09n2.js} +1 -1
  17. package/dist/web/assets/{vendors-DMjSfzlv.js → vendors-CKPV1OAU.js} +2 -2
  18. package/dist/web/assets/{vue-vendor-DET08QYg.js → vue-vendor-3bf-fPGP.js} +1 -1
  19. package/dist/web/index.html +7 -7
  20. package/docs/home.png +0 -0
  21. package/package.json +14 -5
  22. package/src/commands/daemon.js +3 -2
  23. package/src/commands/security.js +1 -2
  24. package/src/commands/toggle-proxy.js +100 -5
  25. package/src/config/paths.js +718 -90
  26. package/src/server/api/agents.js +1 -1
  27. package/src/server/api/channels.js +9 -0
  28. package/src/server/api/claude-hooks.js +13 -8
  29. package/src/server/api/codex-channels.js +9 -0
  30. package/src/server/api/codex-proxy.js +27 -15
  31. package/src/server/api/gemini-proxy.js +22 -11
  32. package/src/server/api/hooks.js +45 -0
  33. package/src/server/api/oauth-credentials.js +163 -0
  34. package/src/server/api/opencode-proxy.js +22 -10
  35. package/src/server/api/plugins.js +2 -1
  36. package/src/server/api/proxy.js +39 -44
  37. package/src/server/api/skills.js +91 -13
  38. package/src/server/api/ui-config.js +5 -0
  39. package/src/server/codex-proxy-server.js +90 -70
  40. package/src/server/gemini-proxy-server.js +107 -88
  41. package/src/server/index.js +2 -0
  42. package/src/server/opencode-proxy-server.js +381 -225
  43. package/src/server/proxy-server.js +86 -60
  44. package/src/server/services/alias.js +3 -3
  45. package/src/server/services/channels.js +21 -24
  46. package/src/server/services/codex-channels.js +158 -255
  47. package/src/server/services/codex-config.js +2 -5
  48. package/src/server/services/codex-env-manager.js +423 -0
  49. package/src/server/services/codex-settings-manager.js +21 -357
  50. package/src/server/services/codex-statistics-service.js +3 -27
  51. package/src/server/services/config-export-service.js +43 -9
  52. package/src/server/services/config-registry-service.js +3 -2
  53. package/src/server/services/config-sync-manager.js +1 -1
  54. package/src/server/services/favorites.js +4 -3
  55. package/src/server/services/gemini-channels.js +14 -12
  56. package/src/server/services/gemini-statistics-service.js +3 -25
  57. package/src/server/services/mcp-service.js +35 -19
  58. package/src/server/services/model-detector.js +4 -3
  59. package/src/server/services/native-keychain.js +243 -0
  60. package/src/server/services/native-oauth-adapters.js +891 -0
  61. package/src/server/services/network-access.js +39 -1
  62. package/src/server/services/notification-hooks.js +951 -0
  63. package/src/server/services/oauth-credentials-service.js +786 -0
  64. package/src/server/services/oauth-utils.js +49 -0
  65. package/src/server/services/opencode-channels.js +19 -15
  66. package/src/server/services/opencode-sessions.js +2 -2
  67. package/src/server/services/opencode-settings-manager.js +169 -16
  68. package/src/server/services/opencode-statistics-service.js +3 -27
  69. package/src/server/services/plugins-service.js +115 -15
  70. package/src/server/services/prompts-service.js +2 -3
  71. package/src/server/services/proxy-log-helper.js +242 -0
  72. package/src/server/services/proxy-runtime.js +6 -4
  73. package/src/server/services/repo-scanner-base.js +12 -4
  74. package/src/server/services/request-logger.js +7 -7
  75. package/src/server/services/security-config.js +4 -4
  76. package/src/server/services/session-cache.js +2 -2
  77. package/src/server/services/sessions.js +2 -2
  78. package/src/server/services/settings-manager.js +13 -0
  79. package/src/server/services/skill-service.js +867 -368
  80. package/src/server/services/statistics-service.js +5 -5
  81. package/src/server/services/ui-config.js +4 -3
  82. package/src/server/services/workspace-service.js +1 -1
  83. package/src/server/websocket-server.js +5 -4
  84. package/dist/web/assets/Home-BsSioaaB.css +0 -1
  85. package/dist/web/assets/Home-obifg_9E.js +0 -1
  86. package/dist/web/assets/index-C7LPdVsN.js +0 -2
  87. package/dist/web/assets/index-eEmjZKWP.css +0 -1
  88. package/docs/bannel.png +0 -0
  89. package/docs/model-redirection.md +0 -251
@@ -13,6 +13,7 @@ const { saveProxyStartTime, clearProxyStartTime, getProxyStartTime, getProxyRunt
13
13
  const { createDecodedStream } = require('./services/response-decoder');
14
14
  const { getEffectiveApiKey } = require('./services/gemini-channels');
15
15
  const { persistProxyRequestSnapshot } = require('./services/request-logger');
16
+ const { publishUsageLog, publishFailureLog } = require('./services/proxy-log-helper');
16
17
 
17
18
  let proxyServer = null;
18
19
  let proxyApp = null;
@@ -186,6 +187,14 @@ async function startGeminiProxyServer(options = {}) {
186
187
  const effectiveKey = getEffectiveApiKey(channel);
187
188
  if (!effectiveKey) {
188
189
  release();
190
+ publishFailureLog({
191
+ source: 'gemini',
192
+ channel: channel.name,
193
+ message: 'API key not configured or expired. Please update your channel key.',
194
+ statusCode: 401,
195
+ stage: 'preflight',
196
+ broadcastLog
197
+ });
189
198
  return res.status(401).json({
190
199
  error: {
191
200
  message: 'API key not configured or expired. Please update your channel key.',
@@ -241,6 +250,20 @@ async function startGeminiProxyServer(options = {}) {
241
250
  release();
242
251
  if (err) {
243
252
  recordFailure(channel.id, 'gemini', err);
253
+ const metadata = requestMetadata.get(req) || {
254
+ channel: channel.name,
255
+ channelId: channel.id,
256
+ startTime: Date.now()
257
+ };
258
+ publishFailureLog({
259
+ source: 'gemini',
260
+ metadata,
261
+ message: err.message,
262
+ error: err,
263
+ statusCode: 502,
264
+ stage: 'proxy_web',
265
+ broadcastLog
266
+ });
244
267
  console.error('Gemini proxy error:', err);
245
268
  if (res && !res.headersSent) {
246
269
  res.status(502).json({
@@ -254,6 +277,13 @@ async function startGeminiProxyServer(options = {}) {
254
277
  });
255
278
  } catch (error) {
256
279
  console.error('Gemini channel allocation error:', error);
280
+ publishFailureLog({
281
+ source: 'gemini',
282
+ message: error.message || 'No Gemini channel available',
283
+ statusCode: 503,
284
+ stage: 'allocate_channel',
285
+ broadcastLog
286
+ });
257
287
  if (!res.headersSent) {
258
288
  res.status(503).json({
259
289
  error: {
@@ -306,8 +336,44 @@ async function startGeminiProxyServer(options = {}) {
306
336
  totalTokens: 0,
307
337
  model: ''
308
338
  };
339
+ let usageRecorded = false;
309
340
  const parsedStream = createDecodedStream(proxyRes);
310
341
 
342
+ function recordUsageIfReady() {
343
+ if (usageRecorded) {
344
+ return false;
345
+ }
346
+
347
+ if (!tokenData.model && metadata.modelFromUrl) {
348
+ tokenData.model = metadata.modelFromUrl;
349
+ }
350
+
351
+ const result = publishUsageLog({
352
+ source: 'gemini',
353
+ metadata,
354
+ model: tokenData.model,
355
+ tokens: {
356
+ input: tokenData.inputTokens,
357
+ output: tokenData.outputTokens,
358
+ cached: tokenData.cachedTokens,
359
+ reasoning: tokenData.reasoningTokens,
360
+ total: tokenData.totalTokens
361
+ },
362
+ calculateCost,
363
+ broadcastLog,
364
+ recordRequest: recordGeminiRequest,
365
+ recordSuccess,
366
+ allowBroadcast: !isResponseClosed
367
+ });
368
+
369
+ if (!result) {
370
+ return false;
371
+ }
372
+
373
+ usageRecorded = true;
374
+ return true;
375
+ }
376
+
311
377
  parsedStream.on('data', (chunk) => {
312
378
  // 如果响应已关闭,停止处理
313
379
  if (isResponseClosed) {
@@ -347,30 +413,27 @@ async function startGeminiProxyServer(options = {}) {
347
413
  }
348
414
 
349
415
  // 提取 usage 信息 (支持 OpenAI 和 Gemini 原生格式)
350
- if (tokenData.inputTokens === 0) {
351
- // OpenAI 格式
352
- if (parsed.usage) {
353
- tokenData.inputTokens = parsed.usage.prompt_tokens || parsed.usage.input_tokens || 0;
354
- tokenData.outputTokens = parsed.usage.completion_tokens || parsed.usage.output_tokens || 0;
355
- tokenData.totalTokens = parsed.usage.total_tokens || 0;
356
-
357
- // Gemini 可能包含缓存信息
358
- if (parsed.usage.prompt_tokens_details) {
359
- tokenData.cachedTokens = parsed.usage.prompt_tokens_details.cached_tokens || 0;
360
- }
416
+ if (parsed.usage) {
417
+ tokenData.inputTokens = parsed.usage.prompt_tokens || parsed.usage.input_tokens || 0;
418
+ tokenData.outputTokens = parsed.usage.completion_tokens || parsed.usage.output_tokens || 0;
419
+ tokenData.totalTokens = parsed.usage.total_tokens || 0;
420
+
421
+ // Gemini 可能包含缓存信息
422
+ if (parsed.usage.prompt_tokens_details) {
423
+ tokenData.cachedTokens = parsed.usage.prompt_tokens_details.cached_tokens || 0;
361
424
  }
362
- // Gemini 原生格式
363
- else if (parsed.usageMetadata) {
364
- tokenData.inputTokens = parsed.usageMetadata.promptTokenCount || 0;
365
- tokenData.outputTokens = parsed.usageMetadata.candidatesTokenCount || 0;
366
- tokenData.totalTokens = parsed.usageMetadata.totalTokenCount || 0;
367
-
368
- // Gemini 缓存信息
369
- if (parsed.usageMetadata.cachedContentTokenCount) {
370
- tokenData.cachedTokens = parsed.usageMetadata.cachedContentTokenCount;
371
- }
425
+ } else if (parsed.usageMetadata) {
426
+ tokenData.inputTokens = parsed.usageMetadata.promptTokenCount || 0;
427
+ tokenData.outputTokens = parsed.usageMetadata.candidatesTokenCount || 0;
428
+ tokenData.totalTokens = parsed.usageMetadata.totalTokenCount || 0;
429
+
430
+ // Gemini 缓存信息
431
+ if (parsed.usageMetadata.cachedContentTokenCount) {
432
+ tokenData.cachedTokens = parsed.usageMetadata.cachedContentTokenCount;
372
433
  }
373
434
  }
435
+
436
+ recordUsageIfReady();
374
437
  } catch (err) {
375
438
  // 忽略解析错误
376
439
  }
@@ -412,73 +475,7 @@ async function startGeminiProxyServer(options = {}) {
412
475
  }
413
476
  }
414
477
 
415
- // 如果没有从响应中提取到模型,使用 URL 中的模型
416
- if (!tokenData.model && metadata.modelFromUrl) {
417
- tokenData.model = metadata.modelFromUrl;
418
- }
419
-
420
- // 记录日志和统计
421
- const now = new Date();
422
- const time = now.toLocaleTimeString('zh-CN', {
423
- hour12: false,
424
- hour: '2-digit',
425
- minute: '2-digit',
426
- second: '2-digit'
427
- });
428
-
429
- // 记录统计数据(先计算)
430
- const tokens = {
431
- input: tokenData.inputTokens,
432
- output: tokenData.outputTokens,
433
- total: tokenData.totalTokens || (tokenData.inputTokens + tokenData.outputTokens)
434
- };
435
- const cost = calculateCost(tokenData.model, tokens);
436
-
437
- // 只有在有 token 数据时才广播日志和记录统计
438
- if (tokenData.inputTokens > 0 || tokenData.outputTokens > 0 || tokenData.totalTokens > 0) {
439
- // 广播日志(仅当响应仍然开放时)
440
- if (!isResponseClosed) {
441
- const logData = {
442
- type: 'log',
443
- id: metadata.id,
444
- time: time,
445
- channel: metadata.channel,
446
- model: tokenData.model,
447
- inputTokens: tokenData.inputTokens,
448
- outputTokens: tokenData.outputTokens,
449
- cachedTokens: tokenData.cachedTokens,
450
- reasoningTokens: tokenData.reasoningTokens,
451
- totalTokens: tokenData.totalTokens || (tokenData.inputTokens + tokenData.outputTokens),
452
- cost: cost,
453
- source: 'gemini'
454
- };
455
-
456
- broadcastLog(logData);
457
- }
458
-
459
- // 记录统计
460
- const duration = Date.now() - metadata.startTime;
461
-
462
- recordGeminiRequest({
463
- id: metadata.id,
464
- timestamp: new Date(metadata.startTime).toISOString(),
465
- toolType: 'gemini',
466
- channel: metadata.channel,
467
- channelId: metadata.channelId,
468
- model: tokenData.model,
469
- tokens: {
470
- input: tokenData.inputTokens,
471
- output: tokenData.outputTokens,
472
- cached: tokenData.cachedTokens,
473
- total: tokens.total
474
- },
475
- duration: duration,
476
- success: true,
477
- cost: cost
478
- });
479
-
480
- recordSuccess(metadata.channelId, 'gemini');
481
- }
478
+ recordUsageIfReady();
482
479
 
483
480
  if (!isResponseClosed) {
484
481
  requestMetadata.delete(req);
@@ -492,6 +489,15 @@ async function startGeminiProxyServer(options = {}) {
492
489
  }
493
490
  isResponseClosed = true;
494
491
  recordFailure(metadata.channelId, 'gemini', err);
492
+ publishFailureLog({
493
+ source: 'gemini',
494
+ metadata,
495
+ message: err.message,
496
+ error: err,
497
+ statusCode: proxyRes.statusCode,
498
+ stage: 'response_stream',
499
+ broadcastLog
500
+ });
495
501
  requestMetadata.delete(req);
496
502
  });
497
503
  });
@@ -504,6 +510,19 @@ async function startGeminiProxyServer(options = {}) {
504
510
  releaseChannel(req.selectedChannel.id, 'gemini');
505
511
  broadcastSchedulerState('gemini', getSchedulerState('gemini'));
506
512
  }
513
+ publishFailureLog({
514
+ source: 'gemini',
515
+ metadata: (req && requestMetadata.get(req)) || {
516
+ channel: req?.selectedChannel?.name,
517
+ channelId: req?.selectedChannel?.id,
518
+ model: req?.body?.model
519
+ },
520
+ message: err.message,
521
+ error: err,
522
+ statusCode: 502,
523
+ stage: 'proxy',
524
+ broadcastLog
525
+ });
507
526
  if (res && !res.headersSent) {
508
527
  res.status(502).json({
509
528
  error: {
@@ -200,6 +200,7 @@ async function startServer(port, host = '127.0.0.1', options = {}) {
200
200
  app.use('/api/skills', require('./api/skills'));
201
201
  const claudeHooks = require('./api/claude-hooks');
202
202
  app.use('/api/claude/hooks', claudeHooks);
203
+ app.use('/api/hooks', require('./api/hooks'));
203
204
 
204
205
  // 初始化 Claude hooks 默认配置(自动开启任务完成通知)
205
206
  claudeHooks.initDefaultHooks();
@@ -217,6 +218,7 @@ async function startServer(port, host = '127.0.0.1', options = {}) {
217
218
 
218
219
  // 配置导出/导入 API
219
220
  app.use('/api/config-export', require('./api/config-export'));
221
+ app.use('/api/oauth-credentials', require('./api/oauth-credentials'));
220
222
 
221
223
  // 配置同步 API
222
224
  app.use('/api/config-sync', require('./api/config-sync'));