@adversity/coding-tool-x 3.0.5 → 3.1.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 +42 -0
- package/dist/web/assets/{icons-BlzwYoRU.js → icons-CO_2OFES.js} +1 -1
- package/dist/web/assets/index-DI8QOi-E.js +14 -0
- package/dist/web/assets/index-uLHGdeZh.css +41 -0
- package/dist/web/assets/{naive-ui-B1TP-0TP.js → naive-ui-B1re3c-e.js} +1 -1
- package/dist/web/index.html +4 -4
- package/package.json +1 -1
- package/src/commands/daemon.js +11 -1
- package/src/commands/ui.js +8 -1
- package/src/index.js +3 -1
- package/src/server/api/channels.js +3 -0
- package/src/server/api/codex-channels.js +40 -0
- package/src/server/api/config-registry.js +341 -0
- package/src/server/api/gemini-channels.js +40 -0
- package/src/server/api/oauth.js +294 -0
- package/src/server/api/permissions.js +30 -15
- package/src/server/codex-proxy-server.js +30 -4
- package/src/server/config/oauth-providers.js +68 -0
- package/src/server/gemini-proxy-server.js +64 -2
- package/src/server/index.js +15 -3
- package/src/server/proxy-server.js +31 -4
- package/src/server/services/channels.js +33 -2
- package/src/server/services/codex-channels.js +35 -4
- package/src/server/services/config-registry-service.js +762 -0
- package/src/server/services/config-sync-manager.js +456 -0
- package/src/server/services/config-templates-service.js +38 -3
- package/src/server/services/gemini-channels.js +40 -1
- package/src/server/services/model-detector.js +116 -23
- package/src/server/services/oauth-callback-server.js +284 -0
- package/src/server/services/oauth-service.js +378 -0
- package/src/server/services/oauth-token-storage.js +135 -0
- package/src/server/services/permission-templates-service.js +0 -31
- package/dist/web/assets/index-19ZPjh5b.css +0 -41
- package/dist/web/assets/index-B4w1yh7H.js +0 -14
|
@@ -13,6 +13,7 @@ const { recordRequest } = require('./services/statistics-service');
|
|
|
13
13
|
const { saveProxyStartTime, clearProxyStartTime, getProxyStartTime, getProxyRuntime } = require('./services/proxy-runtime');
|
|
14
14
|
const eventBus = require('../plugins/event-bus');
|
|
15
15
|
const { CLAUDE_MODEL_PRICING } = require('../config/model-pricing');
|
|
16
|
+
const { getEffectiveApiKey } = require('./services/channels');
|
|
16
17
|
|
|
17
18
|
let proxyServer = null;
|
|
18
19
|
let proxyApp = null;
|
|
@@ -21,6 +22,10 @@ let currentPort = null;
|
|
|
21
22
|
// 用于存储每个请求的元数据(用于 WebSocket 日志)
|
|
22
23
|
const requestMetadata = new Map();
|
|
23
24
|
|
|
25
|
+
// 用于缓存已打印过的模型重定向规则,避免重复打印
|
|
26
|
+
// 格式: { channelId: { "originalModel": "redirectedModel", ... } }
|
|
27
|
+
const printedRedirectCache = new Map();
|
|
28
|
+
|
|
24
29
|
const CLAUDE_BASE_PRICING = DEFAULT_CONFIG.pricing.claude;
|
|
25
30
|
const ONE_MILLION = 1000000;
|
|
26
31
|
|
|
@@ -181,9 +186,10 @@ async function startProxyServer(options = {}) {
|
|
|
181
186
|
});
|
|
182
187
|
|
|
183
188
|
proxyReq.removeHeader('x-api-key');
|
|
184
|
-
|
|
189
|
+
const effectiveKey = getEffectiveApiKey(selectedChannel);
|
|
190
|
+
proxyReq.setHeader('x-api-key', effectiveKey);
|
|
185
191
|
proxyReq.removeHeader('authorization');
|
|
186
|
-
proxyReq.setHeader('authorization', `Bearer ${
|
|
192
|
+
proxyReq.setHeader('authorization', `Bearer ${effectiveKey}`);
|
|
187
193
|
|
|
188
194
|
if (!proxyReq.getHeader('anthropic-version')) {
|
|
189
195
|
proxyReq.setHeader('anthropic-version', '2023-06-01');
|
|
@@ -225,7 +231,14 @@ async function startProxyServer(options = {}) {
|
|
|
225
231
|
req.body.model = redirectedModel;
|
|
226
232
|
// 更新 rawBody 以匹配修改后的 body
|
|
227
233
|
req.rawBody = Buffer.from(JSON.stringify(req.body));
|
|
228
|
-
|
|
234
|
+
|
|
235
|
+
// 只在重定向规则变化时打印日志(避免每次请求都打印)
|
|
236
|
+
const cachedRedirects = printedRedirectCache.get(channel.id) || {};
|
|
237
|
+
if (cachedRedirects[originalModel] !== redirectedModel) {
|
|
238
|
+
cachedRedirects[originalModel] = redirectedModel;
|
|
239
|
+
printedRedirectCache.set(channel.id, cachedRedirects);
|
|
240
|
+
console.log(`[Model Redirect] ${originalModel} → ${redirectedModel} (channel: ${channel.name})`);
|
|
241
|
+
}
|
|
229
242
|
}
|
|
230
243
|
}
|
|
231
244
|
|
|
@@ -526,8 +539,22 @@ function getProxyStatus() {
|
|
|
526
539
|
};
|
|
527
540
|
}
|
|
528
541
|
|
|
542
|
+
/**
|
|
543
|
+
* 清除指定渠道的模型重定向日志缓存
|
|
544
|
+
* 用于在渠道配置更新后触发重新打印日志
|
|
545
|
+
* @param {string} channelId - 渠道 ID
|
|
546
|
+
*/
|
|
547
|
+
function clearRedirectCache(channelId) {
|
|
548
|
+
if (channelId) {
|
|
549
|
+
printedRedirectCache.delete(channelId);
|
|
550
|
+
} else {
|
|
551
|
+
printedRedirectCache.clear();
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
529
555
|
module.exports = {
|
|
530
556
|
startProxyServer,
|
|
531
557
|
stopProxyServer,
|
|
532
|
-
getProxyStatus
|
|
558
|
+
getProxyStatus,
|
|
559
|
+
clearRedirectCache
|
|
533
560
|
};
|
|
@@ -65,6 +65,11 @@ function applyChannelDefaults(channel) {
|
|
|
65
65
|
normalized.enabled = !!normalized.enabled;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
// OAuth 字段默认值(向后兼容)
|
|
69
|
+
if (!normalized.authType) {
|
|
70
|
+
normalized.authType = 'apiKey';
|
|
71
|
+
}
|
|
72
|
+
|
|
68
73
|
normalized.weight = normalizeNumber(normalized.weight, 1, 100);
|
|
69
74
|
|
|
70
75
|
if (normalized.maxConcurrency === undefined ||
|
|
@@ -189,7 +194,11 @@ function createChannel(name, baseUrl, apiKey, websiteUrl, extraConfig = {}) {
|
|
|
189
194
|
modelConfig: extraConfig.modelConfig || null,
|
|
190
195
|
modelRedirects: extraConfig.modelRedirects || [],
|
|
191
196
|
proxyUrl: extraConfig.proxyUrl || '',
|
|
192
|
-
speedTestModel: extraConfig.speedTestModel || null
|
|
197
|
+
speedTestModel: extraConfig.speedTestModel || null,
|
|
198
|
+
// OAuth 支持
|
|
199
|
+
authType: extraConfig.authType || 'apiKey',
|
|
200
|
+
oauthProvider: extraConfig.oauthProvider || null,
|
|
201
|
+
oauthTokenId: extraConfig.oauthTokenId || null
|
|
193
202
|
});
|
|
194
203
|
|
|
195
204
|
data.channels.push(newChannel);
|
|
@@ -381,6 +390,27 @@ function updateClaudeSettings(baseUrl, apiKey) {
|
|
|
381
390
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
382
391
|
}
|
|
383
392
|
|
|
393
|
+
/**
|
|
394
|
+
* 获取渠道的有效 API Key
|
|
395
|
+
* 如果渠道使用 OAuth 认证,返回有效的 OAuth 令牌;否则返回静态 API Key
|
|
396
|
+
*
|
|
397
|
+
* @param {Object} channel - 渠道对象
|
|
398
|
+
* @returns {string|null} 有效的 API Key,OAuth 令牌无效/过期时返回 null
|
|
399
|
+
*/
|
|
400
|
+
function getEffectiveApiKey(channel) {
|
|
401
|
+
if (channel.authType === 'oauth' && channel.oauthTokenId) {
|
|
402
|
+
const { getToken, isTokenExpired } = require('./oauth-token-storage');
|
|
403
|
+
const token = getToken(channel.oauthTokenId);
|
|
404
|
+
if (token && !isTokenExpired(token)) {
|
|
405
|
+
return token.accessToken;
|
|
406
|
+
}
|
|
407
|
+
// OAuth 令牌无效或已过期,返回 null(调用方应处理刷新或报错)
|
|
408
|
+
console.warn(`[Channels] OAuth token expired or not found for channel ${channel.name}`);
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
return channel.apiKey;
|
|
412
|
+
}
|
|
413
|
+
|
|
384
414
|
module.exports = {
|
|
385
415
|
getAllChannels,
|
|
386
416
|
getCurrentSettings,
|
|
@@ -390,5 +420,6 @@ module.exports = {
|
|
|
390
420
|
applyChannelToSettings,
|
|
391
421
|
getBestChannelForRestore,
|
|
392
422
|
updateClaudeSettings,
|
|
393
|
-
updateClaudeSettingsWithModelConfig
|
|
423
|
+
updateClaudeSettingsWithModelConfig,
|
|
424
|
+
getEffectiveApiKey
|
|
394
425
|
};
|
|
@@ -47,7 +47,10 @@ function loadChannels() {
|
|
|
47
47
|
...ch,
|
|
48
48
|
enabled: ch.enabled !== false, // 默认启用
|
|
49
49
|
weight: ch.weight || 1,
|
|
50
|
-
maxConcurrency: ch.maxConcurrency || null
|
|
50
|
+
maxConcurrency: ch.maxConcurrency || null,
|
|
51
|
+
modelRedirects: ch.modelRedirects || [],
|
|
52
|
+
speedTestModel: ch.speedTestModel || null,
|
|
53
|
+
authType: ch.authType || 'apiKey' // 默认 API Key 认证
|
|
51
54
|
}));
|
|
52
55
|
}
|
|
53
56
|
return data;
|
|
@@ -175,6 +178,11 @@ function createChannel(name, providerKey, baseUrl, apiKey, wireApi = 'responses'
|
|
|
175
178
|
enabled: extraConfig.enabled !== false, // 默认启用
|
|
176
179
|
weight: extraConfig.weight || 1,
|
|
177
180
|
maxConcurrency: extraConfig.maxConcurrency || null,
|
|
181
|
+
modelRedirects: extraConfig.modelRedirects || [],
|
|
182
|
+
speedTestModel: extraConfig.speedTestModel || null,
|
|
183
|
+
authType: extraConfig.authType || 'apiKey',
|
|
184
|
+
oauthProvider: extraConfig.oauthProvider || null,
|
|
185
|
+
oauthTokenId: extraConfig.oauthTokenId || null,
|
|
178
186
|
createdAt: Date.now(),
|
|
179
187
|
updatedAt: Date.now()
|
|
180
188
|
};
|
|
@@ -217,11 +225,13 @@ function updateChannel(channelId, updates) {
|
|
|
217
225
|
}
|
|
218
226
|
}
|
|
219
227
|
|
|
228
|
+
const merged = { ...oldChannel, ...updates };
|
|
220
229
|
const newChannel = {
|
|
221
|
-
...
|
|
222
|
-
...updates,
|
|
230
|
+
...merged,
|
|
223
231
|
id: channelId, // 保持 ID 不变
|
|
224
232
|
createdAt: oldChannel.createdAt, // 保持创建时间
|
|
233
|
+
modelRedirects: merged.modelRedirects || [],
|
|
234
|
+
speedTestModel: merged.speedTestModel !== undefined ? merged.speedTestModel : (oldChannel.speedTestModel || null),
|
|
225
235
|
updatedAt: Date.now()
|
|
226
236
|
};
|
|
227
237
|
|
|
@@ -643,6 +653,26 @@ try {
|
|
|
643
653
|
console.warn('[Codex Channels] Auto sync env vars failed:', err.message);
|
|
644
654
|
}
|
|
645
655
|
|
|
656
|
+
/**
|
|
657
|
+
* 获取渠道的有效 API Key
|
|
658
|
+
* 如果渠道使用 OAuth 认证,返回 OAuth 令牌;否则返回静态 API Key
|
|
659
|
+
*
|
|
660
|
+
* @param {Object} channel - 渠道对象
|
|
661
|
+
* @returns {string|null} 有效的 API Key
|
|
662
|
+
*/
|
|
663
|
+
function getEffectiveApiKey(channel) {
|
|
664
|
+
if (channel.authType === 'oauth' && channel.oauthTokenId) {
|
|
665
|
+
const { getToken, isTokenExpired } = require('./oauth-token-storage');
|
|
666
|
+
const token = getToken(channel.oauthTokenId);
|
|
667
|
+
if (token && !isTokenExpired(token)) {
|
|
668
|
+
return token.accessToken;
|
|
669
|
+
}
|
|
670
|
+
// OAuth 令牌无效或已过期,返回 null
|
|
671
|
+
return null;
|
|
672
|
+
}
|
|
673
|
+
return channel.apiKey;
|
|
674
|
+
}
|
|
675
|
+
|
|
646
676
|
module.exports = {
|
|
647
677
|
getChannels,
|
|
648
678
|
createChannel,
|
|
@@ -652,5 +682,6 @@ module.exports = {
|
|
|
652
682
|
saveChannelOrder,
|
|
653
683
|
syncAllChannelEnvVars,
|
|
654
684
|
writeCodexConfigForMultiChannel,
|
|
655
|
-
applyChannelToSettings
|
|
685
|
+
applyChannelToSettings,
|
|
686
|
+
getEffectiveApiKey
|
|
656
687
|
};
|