coding-tool-x 3.3.8 → 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 +17 -2
- package/README.md +253 -326
- package/dist/web/assets/{Analytics-DLpoDZ2M.js → Analytics-D6LzK9hk.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-D_hRb55W.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-JXsyym1s.js → PluginManager-DTgQ--vB.js} +1 -1
- package/dist/web/assets/{ProjectList-DZWSeb-q.js → ProjectList-DMCiGmCT.js} +1 -1
- package/dist/web/assets/{SessionList-Cs624DR3.js → SessionList-CRBsdVRe.js} +1 -1
- package/dist/web/assets/{SkillManager-bEliz7qz.js → SkillManager-DMwx2Q4k.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-J3RecFGn.js → WorkspaceManager-DapB4ljL.js} +1 -1
- package/dist/web/assets/{icons-Cuc23WS7.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 +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/ui-config.js +5 -0
- 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 +41 -87
- package/src/server/services/codex-env-manager.js +423 -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 +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-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,28 @@ function normalizeGatewaySourceType(value, fallback = 'codex') {
|
|
|
32
33
|
return fallback;
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
function buildManagedCodexEnvMap(channels = [], { includeProxyKey = false } = {}) {
|
|
37
|
+
const envMap = {};
|
|
38
|
+
|
|
39
|
+
for (const channel of channels) {
|
|
40
|
+
if (!channel?.envKey || !channel?.apiKey) continue;
|
|
41
|
+
envMap[channel.envKey] = channel.apiKey;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (includeProxyKey) {
|
|
45
|
+
envMap[CODEX_PROXY_ENV_KEY] = CODEX_PROXY_ENV_VALUE;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return envMap;
|
|
49
|
+
}
|
|
50
|
+
|
|
35
51
|
// 获取渠道存储文件路径
|
|
36
52
|
function getChannelsFilePath() {
|
|
37
|
-
const
|
|
38
|
-
if (!fs.existsSync(
|
|
39
|
-
fs.mkdirSync(
|
|
53
|
+
const channelsDir = path.dirname(PATHS.channels.codex);
|
|
54
|
+
if (!fs.existsSync(channelsDir)) {
|
|
55
|
+
fs.mkdirSync(channelsDir, { recursive: true });
|
|
40
56
|
}
|
|
41
|
-
return
|
|
57
|
+
return PATHS.channels.codex;
|
|
42
58
|
}
|
|
43
59
|
|
|
44
60
|
// 读取所有渠道(从我们的存储文件)
|
|
@@ -103,11 +119,11 @@ function initializeFromConfig() {
|
|
|
103
119
|
for (const [providerKey, providerConfig] of Object.entries(config.model_providers)) {
|
|
104
120
|
// env_key 优先级:配置的 env_key > PROVIDER_API_KEY > OPENAI_API_KEY
|
|
105
121
|
let envKey = providerConfig.env_key || `${providerKey.toUpperCase()}_API_KEY`;
|
|
106
|
-
let apiKey = auth[envKey] || '';
|
|
122
|
+
let apiKey = process.env[envKey] || auth[envKey] || '';
|
|
107
123
|
|
|
108
124
|
// 如果没找到,尝试 OPENAI_API_KEY 作为通用 fallback
|
|
109
|
-
if (!apiKey && auth['OPENAI_API_KEY']) {
|
|
110
|
-
apiKey = auth['OPENAI_API_KEY'];
|
|
125
|
+
if (!apiKey && (process.env.OPENAI_API_KEY || auth['OPENAI_API_KEY'])) {
|
|
126
|
+
apiKey = process.env.OPENAI_API_KEY || auth['OPENAI_API_KEY'];
|
|
111
127
|
envKey = 'OPENAI_API_KEY';
|
|
112
128
|
}
|
|
113
129
|
|
|
@@ -129,8 +145,6 @@ function initializeFromConfig() {
|
|
|
129
145
|
createdAt: Date.now(),
|
|
130
146
|
updatedAt: Date.now()
|
|
131
147
|
});
|
|
132
|
-
|
|
133
|
-
// auth.json 已写入 API Key,Codex 启动时优先读取 auth.json,无需注入 shell
|
|
134
148
|
}
|
|
135
149
|
}
|
|
136
150
|
|
|
@@ -276,8 +290,7 @@ function createChannel(name, providerKey, baseUrl, apiKey, wireApi = 'responses'
|
|
|
276
290
|
|
|
277
291
|
data.channels.push(newChannel);
|
|
278
292
|
saveChannels(data);
|
|
279
|
-
|
|
280
|
-
// auth.json 已写入 API Key(通过 writeCodexConfigForMultiChannel),Codex 优先读取 auth.json
|
|
293
|
+
syncAllChannelEnvVars(data.channels);
|
|
281
294
|
|
|
282
295
|
// 注意:不再自动写入 config.toml,只在开启代理控制时才同步
|
|
283
296
|
// writeCodexConfigForMultiChannel(data.channels);
|
|
@@ -342,19 +355,7 @@ function updateChannel(channelId, updates) {
|
|
|
342
355
|
applyChannelToSettings(channelId);
|
|
343
356
|
}
|
|
344
357
|
|
|
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
|
|
358
|
+
syncAllChannelEnvVars(data.channels);
|
|
358
359
|
|
|
359
360
|
// 注意:不再自动写入 config.toml,只在开启代理控制时才同步
|
|
360
361
|
// writeCodexConfigForMultiChannel(data.channels);
|
|
@@ -374,8 +375,7 @@ async function deleteChannel(channelId) {
|
|
|
374
375
|
const deletedChannel = data.channels[index];
|
|
375
376
|
data.channels.splice(index, 1);
|
|
376
377
|
saveChannels(data);
|
|
377
|
-
|
|
378
|
-
// auth.json 中的 key 由 writeCodexConfigForMultiChannel 管理,删除渠道后下次写入时自动清理
|
|
378
|
+
syncAllChannelEnvVars(data.channels);
|
|
379
379
|
|
|
380
380
|
// 注意:不再自动写入 config.toml,只在开启代理控制时才同步
|
|
381
381
|
// writeCodexConfigForMultiChannel(data.channels);
|
|
@@ -399,7 +399,6 @@ function writeCodexConfigForMultiChannel(allChannels) {
|
|
|
399
399
|
}
|
|
400
400
|
|
|
401
401
|
const configPath = path.join(codexDir, 'config.toml');
|
|
402
|
-
const authPath = path.join(codexDir, 'auth.json');
|
|
403
402
|
|
|
404
403
|
// 读取现有配置,保留所有现有字段(特别是 mcp_servers, projects 等)
|
|
405
404
|
const defaultConfig = getDefaultCodexConfig();
|
|
@@ -464,34 +463,10 @@ function writeCodexConfigForMultiChannel(allChannels) {
|
|
|
464
463
|
'# Managed by Coding-Tool',
|
|
465
464
|
'# WARNING: MCP servers and projects are preserved automatically'
|
|
466
465
|
]);
|
|
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 配置文件
|
|
466
|
+
syncCodexUserEnvironment(
|
|
467
|
+
buildManagedCodexEnvMap(allChannels, { includeProxyKey: isProxyMode }),
|
|
468
|
+
{ replace: true }
|
|
469
|
+
);
|
|
495
470
|
}
|
|
496
471
|
|
|
497
472
|
// 获取所有启用的渠道(供调度器使用)
|
|
@@ -529,9 +504,12 @@ function saveChannelOrder(order) {
|
|
|
529
504
|
* 确保用户可以直接使用 codex 命令而无需手动设置环境变量
|
|
530
505
|
* 这个函数会在服务启动时自动调用
|
|
531
506
|
*/
|
|
532
|
-
function syncAllChannelEnvVars() {
|
|
533
|
-
|
|
534
|
-
return
|
|
507
|
+
function syncAllChannelEnvVars(channels = null) {
|
|
508
|
+
const data = channels ? { channels } : loadChannels();
|
|
509
|
+
return syncCodexUserEnvironment(
|
|
510
|
+
buildManagedCodexEnvMap(data.channels || [], { includeProxyKey: isProxyConfig() }),
|
|
511
|
+
{ replace: true }
|
|
512
|
+
);
|
|
535
513
|
}
|
|
536
514
|
|
|
537
515
|
/**
|
|
@@ -565,7 +543,6 @@ function applyChannelToSettings(channelId, options = {}) {
|
|
|
565
543
|
}
|
|
566
544
|
|
|
567
545
|
const configPath = path.join(codexDir, 'config.toml');
|
|
568
|
-
const authPath = path.join(codexDir, 'auth.json');
|
|
569
546
|
|
|
570
547
|
// 读取现有配置,保留 mcp_servers, projects 等
|
|
571
548
|
let config = readCodexConfigOrThrow(configPath, getDefaultCodexConfig());
|
|
@@ -604,30 +581,7 @@ function applyChannelToSettings(channelId, options = {}) {
|
|
|
604
581
|
`# Current provider: ${channel.name}`
|
|
605
582
|
]);
|
|
606
583
|
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
|
|
584
|
+
syncAllChannelEnvVars();
|
|
631
585
|
|
|
632
586
|
return channel;
|
|
633
587
|
}
|