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
|
@@ -18,6 +18,7 @@ const { createDecodedStream } = require('./services/response-decoder');
|
|
|
18
18
|
const eventBus = require('../plugins/event-bus');
|
|
19
19
|
const { getEffectiveApiKey } = require('./services/channels');
|
|
20
20
|
const { persistProxyRequestSnapshot, persistClaudeRequestTemplate } = require('./services/request-logger');
|
|
21
|
+
const { publishUsageLog, publishFailureLog } = require('./services/proxy-log-helper');
|
|
21
22
|
|
|
22
23
|
let proxyServer = null;
|
|
23
24
|
let proxyApp = null;
|
|
@@ -313,20 +314,20 @@ async function startProxyServer(options = {}) {
|
|
|
313
314
|
const effectiveKey = getEffectiveApiKey(channel);
|
|
314
315
|
if (!effectiveKey) {
|
|
315
316
|
release();
|
|
317
|
+
publishFailureLog({
|
|
318
|
+
source: 'claude',
|
|
319
|
+
channel: channel.name,
|
|
320
|
+
message: 'API key not configured or expired. Please update your channel key.',
|
|
321
|
+
statusCode: 401,
|
|
322
|
+
stage: 'preflight',
|
|
323
|
+
broadcastLog
|
|
324
|
+
});
|
|
316
325
|
return res.status(401).json({
|
|
317
326
|
error: 'API key not configured or expired. Please update your channel key.',
|
|
318
327
|
type: 'authentication_error'
|
|
319
328
|
});
|
|
320
329
|
}
|
|
321
330
|
req.effectiveApiKey = effectiveKey;
|
|
322
|
-
|
|
323
|
-
const now = new Date();
|
|
324
|
-
const time = now.toLocaleTimeString('zh-CN', {
|
|
325
|
-
hour12: false,
|
|
326
|
-
hour: '2-digit',
|
|
327
|
-
minute: '2-digit',
|
|
328
|
-
second: '2-digit'
|
|
329
|
-
});
|
|
330
331
|
const requestSnapshot = serializeFullClaudeRequest(req);
|
|
331
332
|
persistClaudeRequestSnapshot({
|
|
332
333
|
timestamp: Date.now(),
|
|
@@ -373,6 +374,21 @@ async function startProxyServer(options = {}) {
|
|
|
373
374
|
if (err) {
|
|
374
375
|
// 记录请求失败
|
|
375
376
|
recordFailure(channel.id, 'claude', err);
|
|
377
|
+
const metadata = requestMetadata.get(req) || {
|
|
378
|
+
id: null,
|
|
379
|
+
channel: channel.name,
|
|
380
|
+
channelId: channel.id,
|
|
381
|
+
startTime: Date.now()
|
|
382
|
+
};
|
|
383
|
+
publishFailureLog({
|
|
384
|
+
source: 'claude',
|
|
385
|
+
metadata,
|
|
386
|
+
message: err.message,
|
|
387
|
+
error: err,
|
|
388
|
+
statusCode: 502,
|
|
389
|
+
stage: 'proxy_web',
|
|
390
|
+
broadcastLog
|
|
391
|
+
});
|
|
376
392
|
console.error('Proxy error:', err);
|
|
377
393
|
if (res && !res.headersSent) {
|
|
378
394
|
res.status(502).json({
|
|
@@ -384,6 +400,13 @@ async function startProxyServer(options = {}) {
|
|
|
384
400
|
});
|
|
385
401
|
} catch (error) {
|
|
386
402
|
console.error('Channel allocation error:', error);
|
|
403
|
+
publishFailureLog({
|
|
404
|
+
source: 'claude',
|
|
405
|
+
message: error.message || '所有渠道暂时不可用',
|
|
406
|
+
statusCode: 503,
|
|
407
|
+
stage: 'allocate_channel',
|
|
408
|
+
broadcastLog
|
|
409
|
+
});
|
|
387
410
|
if (!res.headersSent) {
|
|
388
411
|
res.status(503).json({
|
|
389
412
|
error: error.message || '所有渠道暂时不可用',
|
|
@@ -425,8 +448,34 @@ async function startProxyServer(options = {}) {
|
|
|
425
448
|
cacheRead: 0,
|
|
426
449
|
model: ''
|
|
427
450
|
};
|
|
451
|
+
let usageRecorded = false;
|
|
428
452
|
const parsedStream = createDecodedStream(proxyRes);
|
|
429
453
|
|
|
454
|
+
function recordUsageIfReady() {
|
|
455
|
+
if (usageRecorded) return false;
|
|
456
|
+
|
|
457
|
+
const result = publishUsageLog({
|
|
458
|
+
source: 'claude',
|
|
459
|
+
metadata,
|
|
460
|
+
model: tokenData.model,
|
|
461
|
+
tokens: {
|
|
462
|
+
input: tokenData.inputTokens,
|
|
463
|
+
output: tokenData.outputTokens,
|
|
464
|
+
cacheCreation: tokenData.cacheCreation,
|
|
465
|
+
cacheRead: tokenData.cacheRead
|
|
466
|
+
},
|
|
467
|
+
calculateCost,
|
|
468
|
+
broadcastLog,
|
|
469
|
+
recordRequest,
|
|
470
|
+
recordSuccess,
|
|
471
|
+
allowBroadcast: !isResponseClosed
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
if (!result) return false;
|
|
475
|
+
usageRecorded = true;
|
|
476
|
+
return true;
|
|
477
|
+
}
|
|
478
|
+
|
|
430
479
|
parsedStream.on('data', (chunk) => {
|
|
431
480
|
if (isResponseClosed) return;
|
|
432
481
|
|
|
@@ -474,57 +523,8 @@ async function startProxyServer(options = {}) {
|
|
|
474
523
|
}
|
|
475
524
|
}
|
|
476
525
|
|
|
477
|
-
if (eventType === '
|
|
478
|
-
|
|
479
|
-
const time = now.toLocaleTimeString('zh-CN', {
|
|
480
|
-
hour12: false,
|
|
481
|
-
hour: '2-digit',
|
|
482
|
-
minute: '2-digit',
|
|
483
|
-
second: '2-digit'
|
|
484
|
-
});
|
|
485
|
-
|
|
486
|
-
const tokens = {
|
|
487
|
-
input: tokenData.inputTokens,
|
|
488
|
-
output: tokenData.outputTokens,
|
|
489
|
-
cacheCreation: tokenData.cacheCreation,
|
|
490
|
-
cacheRead: tokenData.cacheRead,
|
|
491
|
-
total: tokenData.inputTokens + tokenData.outputTokens + tokenData.cacheCreation + tokenData.cacheRead
|
|
492
|
-
};
|
|
493
|
-
const cost = calculateCost(tokenData.model, tokens);
|
|
494
|
-
|
|
495
|
-
if (!isResponseClosed) {
|
|
496
|
-
broadcastLog({
|
|
497
|
-
type: 'log',
|
|
498
|
-
id: metadata.id,
|
|
499
|
-
time: time,
|
|
500
|
-
channel: metadata.channel,
|
|
501
|
-
model: tokenData.model,
|
|
502
|
-
inputTokens: tokenData.inputTokens,
|
|
503
|
-
outputTokens: tokenData.outputTokens,
|
|
504
|
-
cacheCreation: tokenData.cacheCreation,
|
|
505
|
-
cacheRead: tokenData.cacheRead,
|
|
506
|
-
cost: cost,
|
|
507
|
-
source: 'claude'
|
|
508
|
-
});
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
const duration = Date.now() - metadata.startTime;
|
|
512
|
-
|
|
513
|
-
recordRequest({
|
|
514
|
-
id: metadata.id,
|
|
515
|
-
timestamp: new Date(metadata.startTime).toISOString(),
|
|
516
|
-
toolType: 'claude-code',
|
|
517
|
-
channel: metadata.channel,
|
|
518
|
-
channelId: metadata.channelId,
|
|
519
|
-
model: tokenData.model,
|
|
520
|
-
tokens: tokens,
|
|
521
|
-
duration: duration,
|
|
522
|
-
success: true,
|
|
523
|
-
cost: cost
|
|
524
|
-
});
|
|
525
|
-
|
|
526
|
-
// 记录请求成功(用于健康检查)
|
|
527
|
-
recordSuccess(metadata.channelId, 'claude');
|
|
526
|
+
if (eventType === 'message_stop') {
|
|
527
|
+
recordUsageIfReady();
|
|
528
528
|
}
|
|
529
529
|
} catch (err) {
|
|
530
530
|
}
|
|
@@ -540,7 +540,10 @@ async function startProxyServer(options = {}) {
|
|
|
540
540
|
}
|
|
541
541
|
};
|
|
542
542
|
|
|
543
|
-
parsedStream.on('end',
|
|
543
|
+
parsedStream.on('end', () => {
|
|
544
|
+
recordUsageIfReady();
|
|
545
|
+
finalize();
|
|
546
|
+
});
|
|
544
547
|
|
|
545
548
|
parsedStream.on('error', (err) => {
|
|
546
549
|
if (err.code !== 'EPIPE' && err.code !== 'ECONNRESET') {
|
|
@@ -550,6 +553,15 @@ async function startProxyServer(options = {}) {
|
|
|
550
553
|
if (metadata && metadata.channelId) {
|
|
551
554
|
recordFailure(metadata.channelId, 'claude', err);
|
|
552
555
|
}
|
|
556
|
+
publishFailureLog({
|
|
557
|
+
source: 'claude',
|
|
558
|
+
metadata,
|
|
559
|
+
message: err.message,
|
|
560
|
+
error: err,
|
|
561
|
+
statusCode: proxyRes.statusCode,
|
|
562
|
+
stage: 'response_stream',
|
|
563
|
+
broadcastLog
|
|
564
|
+
});
|
|
553
565
|
isResponseClosed = true;
|
|
554
566
|
finalize();
|
|
555
567
|
});
|
|
@@ -561,6 +573,20 @@ async function startProxyServer(options = {}) {
|
|
|
561
573
|
if (req && req.selectedChannel && req.selectedChannel.id) {
|
|
562
574
|
recordFailure(req.selectedChannel.id, 'claude', err);
|
|
563
575
|
}
|
|
576
|
+
const metadata = req ? requestMetadata.get(req) : null;
|
|
577
|
+
publishFailureLog({
|
|
578
|
+
source: 'claude',
|
|
579
|
+
metadata: metadata || {
|
|
580
|
+
channel: req?.selectedChannel?.name,
|
|
581
|
+
channelId: req?.selectedChannel?.id,
|
|
582
|
+
model: req?.body?.model
|
|
583
|
+
},
|
|
584
|
+
message: err.message,
|
|
585
|
+
error: err,
|
|
586
|
+
statusCode: 502,
|
|
587
|
+
stage: 'proxy',
|
|
588
|
+
broadcastLog
|
|
589
|
+
});
|
|
564
590
|
if (res && !res.headersSent) {
|
|
565
591
|
res.status(502).json({
|
|
566
592
|
error: 'Proxy error: ' + err.message,
|
|
@@ -2,13 +2,13 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { PATHS } = require('../../config/paths');
|
|
4
4
|
|
|
5
|
-
const ALIAS_DIR = PATHS.base;
|
|
6
5
|
const ALIAS_FILE = PATHS.aliases;
|
|
7
6
|
|
|
8
7
|
// Ensure alias directory exists
|
|
9
8
|
function ensureAliasDir() {
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
const dir = path.dirname(ALIAS_FILE);
|
|
10
|
+
if (!fs.existsSync(dir)) {
|
|
11
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
2
3
|
const { isProxyConfig } = require('./settings-manager');
|
|
3
4
|
const { PATHS, NATIVE_PATHS } = require('../../config/paths');
|
|
5
|
+
const { clearNativeOAuth } = require('./native-oauth-adapters');
|
|
4
6
|
|
|
5
7
|
function getChannelsFilePath() {
|
|
6
|
-
const dir = PATHS.
|
|
8
|
+
const dir = path.dirname(PATHS.channels.claude);
|
|
7
9
|
if (!fs.existsSync(dir)) {
|
|
8
10
|
fs.mkdirSync(dir, { recursive: true });
|
|
9
11
|
}
|
|
@@ -11,7 +13,7 @@ function getChannelsFilePath() {
|
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
function getActiveChannelIdPath() {
|
|
14
|
-
const dir = PATHS.
|
|
16
|
+
const dir = path.dirname(PATHS.activeChannel.claude);
|
|
15
17
|
if (!fs.existsSync(dir)) {
|
|
16
18
|
fs.mkdirSync(dir, { recursive: true });
|
|
17
19
|
}
|
|
@@ -165,11 +167,13 @@ function getCurrentSettings() {
|
|
|
165
167
|
return null;
|
|
166
168
|
}
|
|
167
169
|
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
170
|
+
const nativeOAuth = require('./native-oauth-adapters').readNativeOAuth('claude');
|
|
168
171
|
|
|
169
172
|
let baseUrl = settings.env?.ANTHROPIC_BASE_URL || '';
|
|
170
|
-
let apiKey = settings.env?.ANTHROPIC_API_KEY ||
|
|
171
|
-
|
|
172
|
-
|
|
173
|
+
let apiKey = settings.env?.ANTHROPIC_API_KEY || '';
|
|
174
|
+
if (!apiKey && !nativeOAuth) {
|
|
175
|
+
apiKey = settings.env?.ANTHROPIC_AUTH_TOKEN || '';
|
|
176
|
+
}
|
|
173
177
|
|
|
174
178
|
if (!apiKey && settings.apiKeyHelper) {
|
|
175
179
|
apiKey = extractApiKeyFromHelper(settings.apiKeyHelper);
|
|
@@ -288,14 +292,6 @@ function updateChannel(id, updates) {
|
|
|
288
292
|
console.log(`[Single-channel mode] Enabled "${nextChannel.name}", disabled all others`);
|
|
289
293
|
}
|
|
290
294
|
|
|
291
|
-
// Prevent disabling last enabled channel when proxy is OFF
|
|
292
|
-
if (!isProxyRunning && !nextChannel.enabled && oldChannel.enabled) {
|
|
293
|
-
const enabledCount = data.channels.filter(ch => ch.enabled).length;
|
|
294
|
-
if (enabledCount === 0) {
|
|
295
|
-
throw new Error('无法禁用最后一个启用的渠道。请先启用其他渠道或启动动态切换。');
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
295
|
saveChannels(data);
|
|
300
296
|
|
|
301
297
|
// Sync settings.json only when proxy is OFF.
|
|
@@ -341,6 +337,7 @@ function applyChannelToSettings(id) {
|
|
|
341
337
|
}
|
|
342
338
|
|
|
343
339
|
function updateClaudeSettingsWithModelConfig(channel) {
|
|
340
|
+
clearNativeOAuth('claude');
|
|
344
341
|
const settingsPath = getClaudeSettingsPath();
|
|
345
342
|
|
|
346
343
|
let settings = {};
|
|
@@ -354,17 +351,10 @@ function updateClaudeSettingsWithModelConfig(channel) {
|
|
|
354
351
|
|
|
355
352
|
const { baseUrl, apiKey, modelConfig, presetId, proxyUrl } = channel;
|
|
356
353
|
|
|
357
|
-
const useAuthToken = settings.env.ANTHROPIC_AUTH_TOKEN !== undefined;
|
|
358
|
-
const useApiKey = settings.env.ANTHROPIC_API_KEY !== undefined;
|
|
359
|
-
|
|
360
354
|
settings.env.ANTHROPIC_BASE_URL = baseUrl;
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
delete settings.env.ANTHROPIC_API_KEY;
|
|
365
|
-
} else {
|
|
366
|
-
settings.env.ANTHROPIC_API_KEY = apiKey;
|
|
367
|
-
}
|
|
355
|
+
settings.env.ANTHROPIC_API_KEY = apiKey;
|
|
356
|
+
delete settings.env.ANTHROPIC_AUTH_TOKEN;
|
|
357
|
+
delete settings.env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
368
358
|
|
|
369
359
|
if (presetId && presetId !== 'official' && modelConfig) {
|
|
370
360
|
if (modelConfig.model) {
|
|
@@ -433,6 +423,12 @@ function getEffectiveApiKey(channel) {
|
|
|
433
423
|
return channel.apiKey || null;
|
|
434
424
|
}
|
|
435
425
|
|
|
426
|
+
function disableAllChannels() {
|
|
427
|
+
const data = loadChannels();
|
|
428
|
+
data.channels.forEach(ch => { ch.enabled = false; });
|
|
429
|
+
saveChannels(data);
|
|
430
|
+
}
|
|
431
|
+
|
|
436
432
|
module.exports = {
|
|
437
433
|
getAllChannels,
|
|
438
434
|
getCurrentChannel,
|
|
@@ -444,5 +440,6 @@ module.exports = {
|
|
|
444
440
|
getBestChannelForRestore,
|
|
445
441
|
updateClaudeSettings,
|
|
446
442
|
updateClaudeSettingsWithModelConfig,
|
|
447
|
-
getEffectiveApiKey
|
|
443
|
+
getEffectiveApiKey,
|
|
444
|
+
disableAllChannels
|
|
448
445
|
};
|