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.
- package/CHANGELOG.md +20 -0
- package/README.md +253 -326
- package/dist/web/assets/{Analytics-IW6eAy9u.js → Analytics-D6LzK9hk.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-BPtkTMSc.js → ConfigTemplates-BUDYuxRi.js} +1 -1
- package/dist/web/assets/Home-BQxQ1LhR.css +1 -0
- package/dist/web/assets/Home-D7KX7iF8.js +1 -0
- package/dist/web/assets/{PluginManager-BGx9MSDV.js → PluginManager-DTgQ--vB.js} +1 -1
- package/dist/web/assets/{ProjectList-BCn-mrCx.js → ProjectList-DMCiGmCT.js} +1 -1
- package/dist/web/assets/{SessionList-CzLfebJQ.js → SessionList-CRBsdVRe.js} +1 -1
- package/dist/web/assets/{SkillManager-CXz2vBQx.js → SkillManager-DMwx2Q4k.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-CHtgMfKc.js → WorkspaceManager-DapB4ljL.js} +1 -1
- package/dist/web/assets/{icons-B29onFfZ.js → icons-B5Pl4lrD.js} +1 -1
- package/dist/web/assets/index-CL-qpoJ_.js +2 -0
- package/dist/web/assets/index-D_5dRFOL.css +1 -0
- package/dist/web/assets/{markdown-C9MYpaSi.js → markdown-DyTJGI4N.js} +1 -1
- package/dist/web/assets/{naive-ui-CxpuzdjU.js → naive-ui-Bdxp09n2.js} +1 -1
- package/dist/web/assets/{vendors-DMjSfzlv.js → vendors-CKPV1OAU.js} +2 -2
- package/dist/web/assets/{vue-vendor-DET08QYg.js → vue-vendor-3bf-fPGP.js} +1 -1
- package/dist/web/index.html +7 -7
- package/docs/home.png +0 -0
- package/package.json +14 -5
- package/src/commands/daemon.js +3 -2
- package/src/commands/security.js +1 -2
- package/src/commands/toggle-proxy.js +100 -5
- package/src/config/paths.js +718 -90
- package/src/server/api/agents.js +1 -1
- package/src/server/api/channels.js +9 -0
- package/src/server/api/claude-hooks.js +13 -8
- package/src/server/api/codex-channels.js +9 -0
- package/src/server/api/codex-proxy.js +27 -15
- package/src/server/api/gemini-proxy.js +22 -11
- package/src/server/api/hooks.js +45 -0
- package/src/server/api/oauth-credentials.js +163 -0
- package/src/server/api/opencode-proxy.js +22 -10
- package/src/server/api/plugins.js +2 -1
- package/src/server/api/proxy.js +39 -44
- package/src/server/api/skills.js +91 -13
- package/src/server/api/ui-config.js +5 -0
- package/src/server/codex-proxy-server.js +90 -70
- package/src/server/gemini-proxy-server.js +107 -88
- package/src/server/index.js +2 -0
- package/src/server/opencode-proxy-server.js +381 -225
- package/src/server/proxy-server.js +86 -60
- package/src/server/services/alias.js +3 -3
- package/src/server/services/channels.js +21 -24
- package/src/server/services/codex-channels.js +158 -255
- package/src/server/services/codex-config.js +2 -5
- package/src/server/services/codex-env-manager.js +423 -0
- package/src/server/services/codex-settings-manager.js +21 -357
- package/src/server/services/codex-statistics-service.js +3 -27
- package/src/server/services/config-export-service.js +43 -9
- package/src/server/services/config-registry-service.js +3 -2
- package/src/server/services/config-sync-manager.js +1 -1
- package/src/server/services/favorites.js +4 -3
- package/src/server/services/gemini-channels.js +14 -12
- package/src/server/services/gemini-statistics-service.js +3 -25
- package/src/server/services/mcp-service.js +35 -19
- package/src/server/services/model-detector.js +4 -3
- package/src/server/services/native-keychain.js +243 -0
- package/src/server/services/native-oauth-adapters.js +891 -0
- package/src/server/services/network-access.js +39 -1
- package/src/server/services/notification-hooks.js +951 -0
- package/src/server/services/oauth-credentials-service.js +786 -0
- package/src/server/services/oauth-utils.js +49 -0
- package/src/server/services/opencode-channels.js +19 -15
- package/src/server/services/opencode-sessions.js +2 -2
- package/src/server/services/opencode-settings-manager.js +169 -16
- package/src/server/services/opencode-statistics-service.js +3 -27
- package/src/server/services/plugins-service.js +115 -15
- package/src/server/services/prompts-service.js +2 -3
- package/src/server/services/proxy-log-helper.js +242 -0
- package/src/server/services/proxy-runtime.js +6 -4
- package/src/server/services/repo-scanner-base.js +12 -4
- package/src/server/services/request-logger.js +7 -7
- package/src/server/services/security-config.js +4 -4
- package/src/server/services/session-cache.js +2 -2
- package/src/server/services/sessions.js +2 -2
- package/src/server/services/settings-manager.js +13 -0
- package/src/server/services/skill-service.js +867 -368
- package/src/server/services/statistics-service.js +5 -5
- package/src/server/services/ui-config.js +4 -3
- package/src/server/services/workspace-service.js +1 -1
- package/src/server/websocket-server.js +5 -4
- package/dist/web/assets/Home-BsSioaaB.css +0 -1
- package/dist/web/assets/Home-obifg_9E.js +0 -1
- package/dist/web/assets/index-C7LPdVsN.js +0 -2
- package/dist/web/assets/index-eEmjZKWP.css +0 -1
- package/docs/bannel.png +0 -0
- 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 (
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
-
|
|
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: {
|
package/src/server/index.js
CHANGED
|
@@ -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'));
|