coding-tool-x 3.3.8 → 3.4.0
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 +17 -2
- package/README.md +253 -326
- package/dist/web/assets/{Analytics-DLpoDZ2M.js → Analytics-DEjfL5Jx.js} +4 -4
- package/dist/web/assets/Analytics-RNn1BUbG.css +1 -0
- package/dist/web/assets/{ConfigTemplates-D_hRb55W.js → ConfigTemplates-DkRL_-tf.js} +1 -1
- package/dist/web/assets/Home-BQxQ1LhR.css +1 -0
- package/dist/web/assets/Home-CF-L640I.js +1 -0
- package/dist/web/assets/{PluginManager-JXsyym1s.js → PluginManager-BzNYTdNB.js} +1 -1
- package/dist/web/assets/{ProjectList-DZWSeb-q.js → ProjectList-C0-JgHMM.js} +1 -1
- package/dist/web/assets/{SessionList-Cs624DR3.js → SessionList-CkZUdX5N.js} +1 -1
- package/dist/web/assets/{SkillManager-bEliz7qz.js → SkillManager-Cak0-4d4.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-J3RecFGn.js → WorkspaceManager-CGDJzwEr.js} +1 -1
- package/dist/web/assets/{icons-Cuc23WS7.js → icons-B5Pl4lrD.js} +1 -1
- package/dist/web/assets/index-D_WItvHE.js +2 -0
- package/dist/web/assets/index-Dz7v9OM0.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 +13 -5
- package/src/commands/daemon.js +3 -2
- package/src/commands/security.js +1 -2
- package/src/config/paths.js +638 -93
- package/src/server/api/agents.js +1 -1
- package/src/server/api/claude-hooks.js +13 -8
- package/src/server/api/codex-proxy.js +5 -4
- package/src/server/api/hooks.js +45 -0
- package/src/server/api/plugins.js +0 -1
- package/src/server/api/statistics.js +4 -4
- package/src/server/api/ui-config.js +5 -0
- package/src/server/api/workspaces.js +1 -3
- package/src/server/codex-proxy-server.js +89 -59
- package/src/server/gemini-proxy-server.js +107 -88
- package/src/server/index.js +1 -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 +3 -2
- package/src/server/services/codex-channels.js +38 -87
- package/src/server/services/codex-env-manager.js +426 -0
- package/src/server/services/codex-settings-manager.js +15 -15
- package/src/server/services/codex-statistics-service.js +3 -27
- package/src/server/services/config-export-service.js +20 -7
- 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 +3 -3
- package/src/server/services/gemini-statistics-service.js +3 -25
- package/src/server/services/mcp-service.js +2 -3
- package/src/server/services/model-detector.js +4 -3
- package/src/server/services/native-oauth-adapters.js +2 -1
- package/src/server/services/network-access.js +39 -1
- package/src/server/services/notification-hooks.js +951 -0
- package/src/server/services/opencode-channels.js +6 -6
- package/src/server/services/opencode-sessions.js +2 -2
- package/src/server/services/opencode-statistics-service.js +3 -27
- package/src/server/services/plugins-service.js +110 -31
- 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/skill-service.js +174 -55
- package/src/server/services/statistics-service.js +10 -6
- package/src/server/services/ui-config.js +4 -3
- package/src/server/services/workspace-service.js +101 -156
- package/src/server/websocket-server.js +5 -4
- package/dist/web/assets/Analytics-DuYvId7u.css +0 -1
- package/dist/web/assets/Home-BMoFdAwy.css +0 -1
- package/dist/web/assets/Home-DNwp-0J-.js +0 -1
- package/dist/web/assets/index-BXeSvAwU.js +0 -2
- package/dist/web/assets/index-DWAC3Tdv.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,10 +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');
|
|
4
5
|
const { clearNativeOAuth } = require('./native-oauth-adapters');
|
|
5
6
|
|
|
6
7
|
function getChannelsFilePath() {
|
|
7
|
-
const dir = PATHS.
|
|
8
|
+
const dir = path.dirname(PATHS.channels.claude);
|
|
8
9
|
if (!fs.existsSync(dir)) {
|
|
9
10
|
fs.mkdirSync(dir, { recursive: true });
|
|
10
11
|
}
|
|
@@ -12,7 +13,7 @@ function getChannelsFilePath() {
|
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
function getActiveChannelIdPath() {
|
|
15
|
-
const dir = PATHS.
|
|
16
|
+
const dir = path.dirname(PATHS.activeChannel.claude);
|
|
16
17
|
if (!fs.existsSync(dir)) {
|
|
17
18
|
fs.mkdirSync(dir, { recursive: true });
|
|
18
19
|
}
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const os = require('os');
|
|
4
3
|
const crypto = require('crypto');
|
|
5
4
|
const toml = require('toml');
|
|
6
5
|
const tomlStringify = require('@iarna/toml').stringify;
|
|
7
|
-
const {
|
|
6
|
+
const { PATHS } = require('../../config/paths');
|
|
8
7
|
const { getCodexDir } = require('./codex-config');
|
|
9
8
|
const { isProxyConfig } = require('./codex-settings-manager');
|
|
10
9
|
const { clearNativeOAuth } = require('./native-oauth-adapters');
|
|
10
|
+
const { syncCodexUserEnvironment } = require('./codex-env-manager');
|
|
11
11
|
|
|
12
|
-
const
|
|
12
|
+
const CODEX_PROXY_ENV_KEY = 'CC_PROXY_KEY';
|
|
13
|
+
const CODEX_PROXY_ENV_VALUE = 'PROXY_KEY';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Codex 渠道管理服务(多渠道架构)
|
|
16
17
|
*
|
|
17
18
|
* Codex 配置结构:
|
|
18
19
|
* - config.toml: 主配置,包含 model_provider 和各提供商配置
|
|
19
|
-
* -
|
|
20
|
+
* - 用户级环境变量: env_key 对应的 API Key 存储
|
|
20
21
|
* - 我们的 codex-channels.json: 完整渠道信息(用于管理)
|
|
21
22
|
*
|
|
22
23
|
* 多渠道模式:
|
|
@@ -32,13 +33,25 @@ function normalizeGatewaySourceType(value, fallback = 'codex') {
|
|
|
32
33
|
return fallback;
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
function buildManagedCodexEnvMap(channels = [], { includeProxyKey = false } = {}) {
|
|
37
|
+
// 代理模式:只写代理 Key,不暴露真实 API Key
|
|
38
|
+
if (includeProxyKey) {
|
|
39
|
+
return { [CODEX_PROXY_ENV_KEY]: CODEX_PROXY_ENV_VALUE };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 直连模式:只写当前激活渠道的 Key,环境中有且只有一条记录
|
|
43
|
+
const activeChannel = channels.find(ch => ch.enabled && ch.envKey && ch.apiKey);
|
|
44
|
+
if (!activeChannel) return {};
|
|
45
|
+
return { [activeChannel.envKey]: activeChannel.apiKey };
|
|
46
|
+
}
|
|
47
|
+
|
|
35
48
|
// 获取渠道存储文件路径
|
|
36
49
|
function getChannelsFilePath() {
|
|
37
|
-
const
|
|
38
|
-
if (!fs.existsSync(
|
|
39
|
-
fs.mkdirSync(
|
|
50
|
+
const channelsDir = path.dirname(PATHS.channels.codex);
|
|
51
|
+
if (!fs.existsSync(channelsDir)) {
|
|
52
|
+
fs.mkdirSync(channelsDir, { recursive: true });
|
|
40
53
|
}
|
|
41
|
-
return
|
|
54
|
+
return PATHS.channels.codex;
|
|
42
55
|
}
|
|
43
56
|
|
|
44
57
|
// 读取所有渠道(从我们的存储文件)
|
|
@@ -103,11 +116,11 @@ function initializeFromConfig() {
|
|
|
103
116
|
for (const [providerKey, providerConfig] of Object.entries(config.model_providers)) {
|
|
104
117
|
// env_key 优先级:配置的 env_key > PROVIDER_API_KEY > OPENAI_API_KEY
|
|
105
118
|
let envKey = providerConfig.env_key || `${providerKey.toUpperCase()}_API_KEY`;
|
|
106
|
-
let apiKey = auth[envKey] || '';
|
|
119
|
+
let apiKey = process.env[envKey] || auth[envKey] || '';
|
|
107
120
|
|
|
108
121
|
// 如果没找到,尝试 OPENAI_API_KEY 作为通用 fallback
|
|
109
|
-
if (!apiKey && auth['OPENAI_API_KEY']) {
|
|
110
|
-
apiKey = auth['OPENAI_API_KEY'];
|
|
122
|
+
if (!apiKey && (process.env.OPENAI_API_KEY || auth['OPENAI_API_KEY'])) {
|
|
123
|
+
apiKey = process.env.OPENAI_API_KEY || auth['OPENAI_API_KEY'];
|
|
111
124
|
envKey = 'OPENAI_API_KEY';
|
|
112
125
|
}
|
|
113
126
|
|
|
@@ -129,8 +142,6 @@ function initializeFromConfig() {
|
|
|
129
142
|
createdAt: Date.now(),
|
|
130
143
|
updatedAt: Date.now()
|
|
131
144
|
});
|
|
132
|
-
|
|
133
|
-
// auth.json 已写入 API Key,Codex 启动时优先读取 auth.json,无需注入 shell
|
|
134
145
|
}
|
|
135
146
|
}
|
|
136
147
|
|
|
@@ -276,8 +287,7 @@ function createChannel(name, providerKey, baseUrl, apiKey, wireApi = 'responses'
|
|
|
276
287
|
|
|
277
288
|
data.channels.push(newChannel);
|
|
278
289
|
saveChannels(data);
|
|
279
|
-
|
|
280
|
-
// auth.json 已写入 API Key(通过 writeCodexConfigForMultiChannel),Codex 优先读取 auth.json
|
|
290
|
+
syncAllChannelEnvVars(data.channels);
|
|
281
291
|
|
|
282
292
|
// 注意:不再自动写入 config.toml,只在开启代理控制时才同步
|
|
283
293
|
// writeCodexConfigForMultiChannel(data.channels);
|
|
@@ -342,19 +352,7 @@ function updateChannel(channelId, updates) {
|
|
|
342
352
|
applyChannelToSettings(channelId);
|
|
343
353
|
}
|
|
344
354
|
|
|
345
|
-
|
|
346
|
-
// 如果 envKey 或 apiKey 变化,需要更新环境变量
|
|
347
|
-
const oldEnvKey = oldChannel.envKey;
|
|
348
|
-
const newEnvKey = newChannel.envKey;
|
|
349
|
-
const newApiKey = newChannel.apiKey;
|
|
350
|
-
const shouldRemoveOldEnv =
|
|
351
|
-
!!oldEnvKey && (
|
|
352
|
-
oldEnvKey !== newEnvKey ||
|
|
353
|
-
!newApiKey ||
|
|
354
|
-
newChannel.enabled === false
|
|
355
|
-
);
|
|
356
|
-
|
|
357
|
-
// auth.json 由 applyChannelToSettings/writeCodexConfigForMultiChannel 维护,Codex 优先读取 auth.json
|
|
355
|
+
syncAllChannelEnvVars(data.channels);
|
|
358
356
|
|
|
359
357
|
// 注意:不再自动写入 config.toml,只在开启代理控制时才同步
|
|
360
358
|
// writeCodexConfigForMultiChannel(data.channels);
|
|
@@ -374,8 +372,7 @@ async function deleteChannel(channelId) {
|
|
|
374
372
|
const deletedChannel = data.channels[index];
|
|
375
373
|
data.channels.splice(index, 1);
|
|
376
374
|
saveChannels(data);
|
|
377
|
-
|
|
378
|
-
// auth.json 中的 key 由 writeCodexConfigForMultiChannel 管理,删除渠道后下次写入时自动清理
|
|
375
|
+
syncAllChannelEnvVars(data.channels);
|
|
379
376
|
|
|
380
377
|
// 注意:不再自动写入 config.toml,只在开启代理控制时才同步
|
|
381
378
|
// writeCodexConfigForMultiChannel(data.channels);
|
|
@@ -399,7 +396,6 @@ function writeCodexConfigForMultiChannel(allChannels) {
|
|
|
399
396
|
}
|
|
400
397
|
|
|
401
398
|
const configPath = path.join(codexDir, 'config.toml');
|
|
402
|
-
const authPath = path.join(codexDir, 'auth.json');
|
|
403
399
|
|
|
404
400
|
// 读取现有配置,保留所有现有字段(特别是 mcp_servers, projects 等)
|
|
405
401
|
const defaultConfig = getDefaultCodexConfig();
|
|
@@ -464,34 +460,10 @@ function writeCodexConfigForMultiChannel(allChannels) {
|
|
|
464
460
|
'# Managed by Coding-Tool',
|
|
465
461
|
'# WARNING: MCP servers and projects are preserved automatically'
|
|
466
462
|
]);
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
try {
|
|
472
|
-
auth = JSON.parse(fs.readFileSync(authPath, 'utf8'));
|
|
473
|
-
} catch (err) {
|
|
474
|
-
console.warn('[Codex Channels] Failed to read auth.json, creating new');
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// 更新所有渠道的 API Key
|
|
479
|
-
for (const channel of allChannels) {
|
|
480
|
-
if (channel.envKey && !channel.apiKey) {
|
|
481
|
-
delete auth[channel.envKey];
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
for (const channel of allChannels) {
|
|
486
|
-
if (channel.apiKey) {
|
|
487
|
-
auth[channel.envKey] = channel.apiKey;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
fs.writeFileSync(authPath, JSON.stringify(auth, null, 2), 'utf8');
|
|
492
|
-
|
|
493
|
-
// 注意:环境变量注入在 createChannel 和 updateChannel 时已经处理
|
|
494
|
-
// 这里不再重复注入,避免多次写入 shell 配置文件
|
|
463
|
+
syncCodexUserEnvironment(
|
|
464
|
+
buildManagedCodexEnvMap(allChannels, { includeProxyKey: isProxyMode }),
|
|
465
|
+
{ replace: true }
|
|
466
|
+
);
|
|
495
467
|
}
|
|
496
468
|
|
|
497
469
|
// 获取所有启用的渠道(供调度器使用)
|
|
@@ -529,9 +501,12 @@ function saveChannelOrder(order) {
|
|
|
529
501
|
* 确保用户可以直接使用 codex 命令而无需手动设置环境变量
|
|
530
502
|
* 这个函数会在服务启动时自动调用
|
|
531
503
|
*/
|
|
532
|
-
function syncAllChannelEnvVars() {
|
|
533
|
-
|
|
534
|
-
return
|
|
504
|
+
function syncAllChannelEnvVars(channels = null) {
|
|
505
|
+
const data = channels ? { channels } : loadChannels();
|
|
506
|
+
return syncCodexUserEnvironment(
|
|
507
|
+
buildManagedCodexEnvMap(data.channels || [], { includeProxyKey: isProxyConfig() }),
|
|
508
|
+
{ replace: true }
|
|
509
|
+
);
|
|
535
510
|
}
|
|
536
511
|
|
|
537
512
|
/**
|
|
@@ -565,7 +540,6 @@ function applyChannelToSettings(channelId, options = {}) {
|
|
|
565
540
|
}
|
|
566
541
|
|
|
567
542
|
const configPath = path.join(codexDir, 'config.toml');
|
|
568
|
-
const authPath = path.join(codexDir, 'auth.json');
|
|
569
543
|
|
|
570
544
|
// 读取现有配置,保留 mcp_servers, projects 等
|
|
571
545
|
let config = readCodexConfigOrThrow(configPath, getDefaultCodexConfig());
|
|
@@ -604,30 +578,7 @@ function applyChannelToSettings(channelId, options = {}) {
|
|
|
604
578
|
`# Current provider: ${channel.name}`
|
|
605
579
|
]);
|
|
606
580
|
console.log(`[Codex Channels] Applied channel ${channel.name} to config.toml`);
|
|
607
|
-
|
|
608
|
-
// 更新 auth.json
|
|
609
|
-
let auth = {};
|
|
610
|
-
if (fs.existsSync(authPath)) {
|
|
611
|
-
try {
|
|
612
|
-
auth = JSON.parse(fs.readFileSync(authPath, 'utf8'));
|
|
613
|
-
} catch (err) {
|
|
614
|
-
console.warn('[Codex Channels] Failed to read auth.json, creating new');
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
if (channel.apiKey && channel.envKey) {
|
|
619
|
-
auth[channel.envKey] = channel.apiKey;
|
|
620
|
-
} else if (channel.envKey) {
|
|
621
|
-
delete auth[channel.envKey];
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
// 清除 chatgpt token 认证字段,避免 Codex 优先用过期 token 而报 usage limit
|
|
625
|
-
delete auth.tokens;
|
|
626
|
-
delete auth.auth_mode;
|
|
627
|
-
|
|
628
|
-
fs.writeFileSync(authPath, JSON.stringify(auth, null, 2), 'utf8');
|
|
629
|
-
|
|
630
|
-
// auth.json 已在上方写入 API Key,Codex 优先读取 auth.json,无需注入 shell
|
|
581
|
+
syncAllChannelEnvVars();
|
|
631
582
|
|
|
632
583
|
return channel;
|
|
633
584
|
}
|