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
package/src/server/api/agents.js
CHANGED
|
@@ -105,7 +105,7 @@ function getAllowedProjectRoots() {
|
|
|
105
105
|
|
|
106
106
|
// 从工作区配置中扩展允许目录,避免误拦截外部磁盘/自定义根目录项目
|
|
107
107
|
try {
|
|
108
|
-
const workspaceConfigPath =
|
|
108
|
+
const workspaceConfigPath = PATHS.workspaces;
|
|
109
109
|
if (fs.existsSync(workspaceConfigPath)) {
|
|
110
110
|
const raw = fs.readFileSync(workspaceConfigPath, 'utf-8');
|
|
111
111
|
const parsed = JSON.parse(raw || '{}');
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
2
|
const router = express.Router();
|
|
3
|
+
const fs = require('fs');
|
|
3
4
|
const {
|
|
4
5
|
getAllChannels,
|
|
5
6
|
applyChannelToSettings,
|
|
@@ -18,6 +19,8 @@ const {
|
|
|
18
19
|
sanitizeBatchConcurrency,
|
|
19
20
|
runWithConcurrencyLimit
|
|
20
21
|
} = require('../services/speed-test');
|
|
22
|
+
const { PATHS } = require('../../config/paths');
|
|
23
|
+
const { deleteBackup } = require('../services/settings-manager');
|
|
21
24
|
const { getDefaultSpeedTestModelByToolType } = require('../../config/model-metadata');
|
|
22
25
|
const { broadcastLog, broadcastProxyState, broadcastSchedulerState } = require('../websocket-server');
|
|
23
26
|
const { clearRedirectCache } = require('../proxy-server');
|
|
@@ -185,6 +188,12 @@ router.post('/:id/apply-to-settings', async (req, res) => {
|
|
|
185
188
|
// Stop proxy and restore backup
|
|
186
189
|
const { stopProxyServer } = require('../proxy-server');
|
|
187
190
|
await stopProxyServer({ clearStartTime: false });
|
|
191
|
+
deleteBackup();
|
|
192
|
+
try {
|
|
193
|
+
fs.unlinkSync(PATHS.activeChannel.claude);
|
|
194
|
+
} catch {
|
|
195
|
+
// ignore missing active channel marker
|
|
196
|
+
}
|
|
188
197
|
|
|
189
198
|
// Re-apply channel settings after proxy stop to prevent race condition
|
|
190
199
|
// (stopProxyServer restores backup, then we overwrite it with current channel)
|
|
@@ -5,21 +5,26 @@ const path = require('path');
|
|
|
5
5
|
const os = require('os');
|
|
6
6
|
const https = require('https');
|
|
7
7
|
const http = require('http');
|
|
8
|
+
const { PATHS, NATIVE_PATHS } = require('../../config/paths');
|
|
8
9
|
const { resolvePreferredHomeDir, normalizeWindowsHomePath } = require('../../utils/home-dir');
|
|
10
|
+
const { createSameOriginGuard } = require('../services/network-access');
|
|
9
11
|
|
|
10
12
|
// 检测操作系统
|
|
11
13
|
const platform = os.platform(); // 'darwin' | 'win32' | 'linux'
|
|
14
|
+
router.use(createSameOriginGuard({
|
|
15
|
+
message: '禁止跨站访问 Claude Hooks 配置接口'
|
|
16
|
+
}));
|
|
12
17
|
|
|
13
18
|
const HOME_DIR = resolvePreferredHomeDir(platform, process.env, os.homedir());
|
|
14
19
|
|
|
15
20
|
// Claude settings.json 路径
|
|
16
|
-
const CLAUDE_SETTINGS_PATH =
|
|
21
|
+
const CLAUDE_SETTINGS_PATH = NATIVE_PATHS.claude.settings;
|
|
17
22
|
|
|
18
23
|
// UI 配置路径(记录用户是否主动关闭过、飞书配置等)
|
|
19
|
-
const UI_CONFIG_PATH =
|
|
24
|
+
const UI_CONFIG_PATH = PATHS.uiConfig;
|
|
20
25
|
|
|
21
26
|
// 通知脚本路径(用于飞书通知)
|
|
22
|
-
const NOTIFY_SCRIPT_PATH =
|
|
27
|
+
const NOTIFY_SCRIPT_PATH = PATHS.notifyHook;
|
|
23
28
|
|
|
24
29
|
// 读取 Claude settings.json
|
|
25
30
|
function readClaudeSettings() {
|
|
@@ -222,17 +227,17 @@ function shouldRepairStopHook(settings, expectedScriptPath = NOTIFY_SCRIPT_PATH,
|
|
|
222
227
|
return false;
|
|
223
228
|
}
|
|
224
229
|
|
|
225
|
-
const markerType = parseNotifyTypeMarker(command);
|
|
226
|
-
if (!markerType) {
|
|
227
|
-
return false;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
230
|
const normalizedCommand = normalizePathForCompare(command);
|
|
231
231
|
const normalizedExpected = normalizePathForCompare(expectedScriptPath);
|
|
232
232
|
if (!normalizedCommand.includes(normalizedExpected)) {
|
|
233
233
|
return true;
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
+
const markerType = parseNotifyTypeMarker(command);
|
|
237
|
+
if (!markerType) {
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
|
|
236
241
|
return !fileExists(expectedScriptPath);
|
|
237
242
|
}
|
|
238
243
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
2
|
const router = express.Router();
|
|
3
|
+
const fs = require('fs');
|
|
3
4
|
const {
|
|
4
5
|
getChannels,
|
|
5
6
|
createChannel,
|
|
@@ -20,6 +21,8 @@ const {
|
|
|
20
21
|
runWithConcurrencyLimit
|
|
21
22
|
} = require('../services/speed-test');
|
|
22
23
|
const { clearCodexRedirectCache } = require('../codex-proxy-server');
|
|
24
|
+
const { deleteBackup } = require('../services/codex-settings-manager');
|
|
25
|
+
const { PATHS } = require('../../config/paths');
|
|
23
26
|
const { getDefaultSpeedTestModelByToolType } = require('../../config/model-metadata');
|
|
24
27
|
const CODEX_GATEWAY_SOURCE_TYPE = 'codex';
|
|
25
28
|
|
|
@@ -349,6 +352,12 @@ module.exports = (config) => {
|
|
|
349
352
|
if (proxyStatus && proxyStatus.running) {
|
|
350
353
|
console.log(`Codex proxy is running, stopping to apply channel settings: ${channel.name}`);
|
|
351
354
|
await stopCodexProxyServer({ clearStartTime: false });
|
|
355
|
+
deleteBackup();
|
|
356
|
+
try {
|
|
357
|
+
fs.unlinkSync(PATHS.activeChannel.codex);
|
|
358
|
+
} catch {
|
|
359
|
+
// ignore missing active channel marker
|
|
360
|
+
}
|
|
352
361
|
|
|
353
362
|
broadcastLog({
|
|
354
363
|
type: 'action',
|
|
@@ -15,6 +15,7 @@ const {
|
|
|
15
15
|
readConfig
|
|
16
16
|
} = require('../services/codex-settings-manager');
|
|
17
17
|
const { getChannels, getEnabledChannels } = require('../services/codex-channels');
|
|
18
|
+
const { clearNativeOAuth } = require('../services/native-oauth-adapters');
|
|
18
19
|
const { clearAllLogs } = require('../websocket-server');
|
|
19
20
|
const { PATHS, ensureStorageDirMigrated } = require('../../config/paths');
|
|
20
21
|
const fs = require('fs');
|
|
@@ -42,6 +43,24 @@ function selectLatestEnabledChannel(channels) {
|
|
|
42
43
|
}, enabledChannels[0]);
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
function resolveActiveChannel(channels, activeChannelId = null) {
|
|
47
|
+
if (!Array.isArray(channels) || channels.length === 0) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (activeChannelId) {
|
|
52
|
+
const matched = channels.find(channel => channel.id === activeChannelId);
|
|
53
|
+
if (matched) {
|
|
54
|
+
return matched;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return selectLatestEnabledChannel(channels)
|
|
59
|
+
|| channels.find(channel => channel.enabled !== false)
|
|
60
|
+
|| channels[0]
|
|
61
|
+
|| null;
|
|
62
|
+
}
|
|
63
|
+
|
|
45
64
|
// 保存激活渠道ID
|
|
46
65
|
function saveActiveChannelId(channelId) {
|
|
47
66
|
ensureStorageDirMigrated();
|
|
@@ -82,8 +101,7 @@ router.get('/status', (req, res) => {
|
|
|
82
101
|
try {
|
|
83
102
|
const proxyStatus = getCodexProxyStatus();
|
|
84
103
|
const { channels } = getChannels();
|
|
85
|
-
const
|
|
86
|
-
const activeChannel = enabledChannels[0]; // 多渠道模式:第一个启用的渠道
|
|
104
|
+
const activeChannel = resolveActiveChannel(channels, loadActiveChannelId());
|
|
87
105
|
const configStatus = {
|
|
88
106
|
isProxyConfig: isProxyConfig(),
|
|
89
107
|
configExists: configExists(),
|
|
@@ -143,6 +161,7 @@ router.post('/start', async (req, res) => {
|
|
|
143
161
|
}
|
|
144
162
|
|
|
145
163
|
// 5. 设置代理配置(备份并修改 config.toml 和 auth.json)
|
|
164
|
+
clearNativeOAuth('codex');
|
|
146
165
|
const configResult = setProxyConfig(proxyResult.port);
|
|
147
166
|
|
|
148
167
|
const updatedStatus = getCodexProxyStatus();
|
|
@@ -155,11 +174,12 @@ router.post('/start', async (req, res) => {
|
|
|
155
174
|
let message = `Codex proxy started on port ${proxyResult.port}, active channel: ${currentChannel.name}`;
|
|
156
175
|
let envHint = null;
|
|
157
176
|
|
|
158
|
-
|
|
159
|
-
if (configResult.envInjected && configResult.isFirstTime) {
|
|
177
|
+
if (configResult.envInjected && configResult.reloadRequired) {
|
|
160
178
|
envHint = {
|
|
161
|
-
command: configResult.sourceCommand,
|
|
162
|
-
message:
|
|
179
|
+
command: configResult.sourceCommand || null,
|
|
180
|
+
message: configResult.sourceCommand
|
|
181
|
+
? `请在 Codex 终端执行: ${configResult.sourceCommand}`
|
|
182
|
+
: '请重新打开 Codex 终端以加载新的用户环境变量'
|
|
163
183
|
};
|
|
164
184
|
}
|
|
165
185
|
|
|
@@ -179,17 +199,9 @@ router.post('/start', async (req, res) => {
|
|
|
179
199
|
// 停止代理
|
|
180
200
|
router.post('/stop', async (req, res) => {
|
|
181
201
|
try {
|
|
182
|
-
// 1. 获取当前激活渠道(优先使用启动动态切换时记录的渠道ID)
|
|
183
202
|
const { channels } = getChannels();
|
|
184
203
|
const activeChannelId = loadActiveChannelId();
|
|
185
|
-
|
|
186
|
-
if (!activeChannel && activeChannelId) {
|
|
187
|
-
activeChannel = channels.find(ch => ch.id === activeChannelId);
|
|
188
|
-
}
|
|
189
|
-
if (!activeChannel) {
|
|
190
|
-
const enabledChannels = channels.filter(ch => ch.enabled !== false);
|
|
191
|
-
activeChannel = enabledChannels[0] || channels[0] || null;
|
|
192
|
-
}
|
|
204
|
+
const activeChannel = resolveActiveChannel(channels, activeChannelId);
|
|
193
205
|
|
|
194
206
|
// 2. 停止代理服务器
|
|
195
207
|
const proxyResult = await stopCodexProxyServer();
|
|
@@ -16,6 +16,7 @@ const {
|
|
|
16
16
|
readEnv
|
|
17
17
|
} = require('../services/gemini-settings-manager');
|
|
18
18
|
const { getChannels, getEnabledChannels } = require('../services/gemini-channels');
|
|
19
|
+
const { clearNativeOAuth } = require('../services/native-oauth-adapters');
|
|
19
20
|
const { PATHS, ensureStorageDirMigrated } = require('../../config/paths');
|
|
20
21
|
const fs = require('fs');
|
|
21
22
|
const path = require('path');
|
|
@@ -37,6 +38,24 @@ function selectLatestEnabledChannel(channels) {
|
|
|
37
38
|
}, enabledChannels[0]);
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
function resolveActiveChannel(channels, activeChannelId = null) {
|
|
42
|
+
if (!Array.isArray(channels) || channels.length === 0) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (activeChannelId) {
|
|
47
|
+
const matched = channels.find(channel => channel.id === activeChannelId);
|
|
48
|
+
if (matched) {
|
|
49
|
+
return matched;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return selectLatestEnabledChannel(channels)
|
|
54
|
+
|| channels.find(channel => channel.enabled !== false)
|
|
55
|
+
|| channels[0]
|
|
56
|
+
|| null;
|
|
57
|
+
}
|
|
58
|
+
|
|
40
59
|
// 保存激活渠道ID
|
|
41
60
|
function saveActiveChannelId(channelId) {
|
|
42
61
|
ensureStorageDirMigrated();
|
|
@@ -83,8 +102,7 @@ router.get('/status', (req, res) => {
|
|
|
83
102
|
currentProxyPort: getCurrentProxyPort()
|
|
84
103
|
};
|
|
85
104
|
const { channels } = getChannels();
|
|
86
|
-
const
|
|
87
|
-
const activeChannel = enabledChannels[0]; // 多渠道模式:第一个启用的渠道
|
|
105
|
+
const activeChannel = resolveActiveChannel(channels, loadActiveChannelId());
|
|
88
106
|
|
|
89
107
|
res.json({
|
|
90
108
|
proxy: proxyStatus,
|
|
@@ -141,6 +159,7 @@ router.post('/start', async (req, res) => {
|
|
|
141
159
|
}
|
|
142
160
|
|
|
143
161
|
// 5. 设置代理配置(备份并修改 .env 和 settings.json)
|
|
162
|
+
clearNativeOAuth('gemini');
|
|
144
163
|
setProxyConfig(proxyResult.port);
|
|
145
164
|
|
|
146
165
|
const { broadcastProxyState } = require('../websocket-server');
|
|
@@ -164,17 +183,9 @@ router.post('/start', async (req, res) => {
|
|
|
164
183
|
// 停止代理
|
|
165
184
|
router.post('/stop', async (req, res) => {
|
|
166
185
|
try {
|
|
167
|
-
// 1. 获取当前激活渠道(优先使用启动动态切换时记录的渠道ID)
|
|
168
186
|
const { channels } = getChannels();
|
|
169
187
|
const activeChannelId = loadActiveChannelId();
|
|
170
|
-
|
|
171
|
-
if (!activeChannel && activeChannelId) {
|
|
172
|
-
activeChannel = channels.find(ch => ch.id === activeChannelId);
|
|
173
|
-
}
|
|
174
|
-
if (!activeChannel) {
|
|
175
|
-
const enabledChannels = channels.filter(ch => ch.enabled !== false);
|
|
176
|
-
activeChannel = enabledChannels[0] || channels[0] || null;
|
|
177
|
-
}
|
|
188
|
+
const activeChannel = resolveActiveChannel(channels, activeChannelId);
|
|
178
189
|
|
|
179
190
|
// 2. 停止代理服务器
|
|
180
191
|
const proxyResult = await stopGeminiProxyServer();
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const router = express.Router();
|
|
3
|
+
const notificationHooks = require('../services/notification-hooks');
|
|
4
|
+
const { createSameOriginGuard } = require('../services/network-access');
|
|
5
|
+
|
|
6
|
+
router.use(createSameOriginGuard({
|
|
7
|
+
message: '禁止跨站访问通知配置接口'
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
router.get('/', (req, res) => {
|
|
11
|
+
try {
|
|
12
|
+
res.json(notificationHooks.getNotificationSettings());
|
|
13
|
+
} catch (error) {
|
|
14
|
+
console.error('Error getting notification hook settings:', error);
|
|
15
|
+
res.status(error.statusCode || 500).json({ error: error.message });
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
router.post('/', (req, res) => {
|
|
20
|
+
try {
|
|
21
|
+
const result = notificationHooks.saveNotificationSettings(req.body || {});
|
|
22
|
+
res.json({
|
|
23
|
+
...result,
|
|
24
|
+
message: '通知设置已保存'
|
|
25
|
+
});
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error('Error saving notification hook settings:', error);
|
|
28
|
+
res.status(error.statusCode || 500).json({ error: error.message });
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
router.post('/test', async (req, res) => {
|
|
33
|
+
try {
|
|
34
|
+
await notificationHooks.testNotification(req.body || {});
|
|
35
|
+
res.json({
|
|
36
|
+
success: true,
|
|
37
|
+
message: req.body?.testFeishu ? '飞书测试通知已发送' : '系统测试通知已发送'
|
|
38
|
+
});
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error('Error testing notification hook settings:', error);
|
|
41
|
+
res.status(error.statusCode || 500).json({ error: error.message });
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
module.exports = router;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const router = express.Router();
|
|
3
|
+
const {
|
|
4
|
+
SUPPORTED_TOOLS,
|
|
5
|
+
getAllToolSummaries,
|
|
6
|
+
getToolSummary,
|
|
7
|
+
importCredential,
|
|
8
|
+
syncLocalCredential,
|
|
9
|
+
setDefaultCredential,
|
|
10
|
+
deleteCredential,
|
|
11
|
+
applyStoredCredential,
|
|
12
|
+
clearNativeOAuthState,
|
|
13
|
+
fetchCredentialUsage
|
|
14
|
+
} = require('../services/oauth-credentials-service');
|
|
15
|
+
|
|
16
|
+
function assertTool(tool) {
|
|
17
|
+
if (!SUPPORTED_TOOLS.includes(tool)) {
|
|
18
|
+
const error = new Error(`Unsupported OAuth tool: ${tool}`);
|
|
19
|
+
error.statusCode = 404;
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function broadcastToolProxyState(tool) {
|
|
25
|
+
const { broadcastProxyState } = require('../websocket-server');
|
|
26
|
+
|
|
27
|
+
if (tool === 'claude') {
|
|
28
|
+
const { getProxyStatus } = require('../proxy-server');
|
|
29
|
+
const { getAllChannels } = require('../services/channels');
|
|
30
|
+
const channels = getAllChannels();
|
|
31
|
+
const activeChannel = channels.find(ch => ch.enabled !== false) || null;
|
|
32
|
+
broadcastProxyState('claude', getProxyStatus(), activeChannel, channels);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (tool === 'codex') {
|
|
37
|
+
const { getCodexProxyStatus } = require('../codex-proxy-server');
|
|
38
|
+
const { getChannels } = require('../services/codex-channels');
|
|
39
|
+
const channels = getChannels().channels || [];
|
|
40
|
+
const activeChannel = channels.find(ch => ch.enabled !== false) || null;
|
|
41
|
+
broadcastProxyState('codex', getCodexProxyStatus(), activeChannel, channels);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (tool === 'gemini') {
|
|
46
|
+
const { getGeminiProxyStatus } = require('../gemini-proxy-server');
|
|
47
|
+
const { getChannels } = require('../services/gemini-channels');
|
|
48
|
+
const channels = getChannels().channels || [];
|
|
49
|
+
const activeChannel = channels.find(ch => ch.enabled !== false) || null;
|
|
50
|
+
broadcastProxyState('gemini', getGeminiProxyStatus(), activeChannel, channels);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (tool === 'opencode') {
|
|
55
|
+
const { getOpenCodeProxyStatus } = require('../opencode-proxy-server');
|
|
56
|
+
const { getChannels } = require('../services/opencode-channels');
|
|
57
|
+
const channels = getChannels().channels || [];
|
|
58
|
+
const activeChannel = channels.find(ch => ch.enabled !== false) || null;
|
|
59
|
+
broadcastProxyState('opencode', getOpenCodeProxyStatus(), activeChannel, channels);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
router.get('/', (req, res) => {
|
|
64
|
+
try {
|
|
65
|
+
res.json({ tools: getAllToolSummaries() });
|
|
66
|
+
} catch (error) {
|
|
67
|
+
res.status(500).json({ error: error.message });
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
router.get('/:tool', (req, res) => {
|
|
72
|
+
try {
|
|
73
|
+
const { tool } = req.params;
|
|
74
|
+
assertTool(tool);
|
|
75
|
+
res.json({ tool, summary: getToolSummary(tool) });
|
|
76
|
+
} catch (error) {
|
|
77
|
+
res.status(error.statusCode || 500).json({ error: error.message });
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
router.post('/:tool/import', (req, res) => {
|
|
82
|
+
try {
|
|
83
|
+
const { tool } = req.params;
|
|
84
|
+
assertTool(tool);
|
|
85
|
+
const credential = importCredential(tool, req.body || {});
|
|
86
|
+
res.json({ tool, credential, summary: getToolSummary(tool) });
|
|
87
|
+
} catch (error) {
|
|
88
|
+
res.status(error.statusCode || 500).json({ error: error.message });
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
router.post('/:tool/sync-local', (req, res) => {
|
|
93
|
+
try {
|
|
94
|
+
const { tool } = req.params;
|
|
95
|
+
assertTool(tool);
|
|
96
|
+
const result = syncLocalCredential(tool);
|
|
97
|
+
res.json({ tool, ...result });
|
|
98
|
+
} catch (error) {
|
|
99
|
+
res.status(error.statusCode || 500).json({ error: error.message });
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
router.post('/:tool/:credentialId/default', (req, res) => {
|
|
104
|
+
try {
|
|
105
|
+
const { tool, credentialId } = req.params;
|
|
106
|
+
assertTool(tool);
|
|
107
|
+
const summary = setDefaultCredential(tool, credentialId);
|
|
108
|
+
res.json({ tool, summary });
|
|
109
|
+
} catch (error) {
|
|
110
|
+
res.status(error.statusCode || 500).json({ error: error.message });
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
router.post('/:tool/:credentialId/apply', async (req, res) => {
|
|
115
|
+
try {
|
|
116
|
+
const { tool, credentialId } = req.params;
|
|
117
|
+
assertTool(tool);
|
|
118
|
+
const result = await applyStoredCredential(tool, credentialId);
|
|
119
|
+
broadcastToolProxyState(tool);
|
|
120
|
+
res.json({
|
|
121
|
+
tool,
|
|
122
|
+
...result,
|
|
123
|
+
message: `${tool} 已切换到 OAuth 凭证控制`
|
|
124
|
+
});
|
|
125
|
+
} catch (error) {
|
|
126
|
+
res.status(error.statusCode || 500).json({ error: error.message });
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
router.post('/:tool/clear-native', (req, res) => {
|
|
131
|
+
try {
|
|
132
|
+
const { tool } = req.params;
|
|
133
|
+
assertTool(tool);
|
|
134
|
+
const nativeState = clearNativeOAuthState(tool);
|
|
135
|
+
res.json({ tool, nativeState });
|
|
136
|
+
} catch (error) {
|
|
137
|
+
res.status(error.statusCode || 500).json({ error: error.message });
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
router.get('/:tool/:credentialId/usage', async (req, res) => {
|
|
142
|
+
try {
|
|
143
|
+
const { tool, credentialId } = req.params;
|
|
144
|
+
assertTool(tool);
|
|
145
|
+
const result = await fetchCredentialUsage(tool, credentialId);
|
|
146
|
+
res.json({ tool, credentialId, usage: result });
|
|
147
|
+
} catch (error) {
|
|
148
|
+
res.status(error.statusCode || 500).json({ error: error.message });
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
router.delete('/:tool/:credentialId', (req, res) => {
|
|
153
|
+
try {
|
|
154
|
+
const { tool, credentialId } = req.params;
|
|
155
|
+
assertTool(tool);
|
|
156
|
+
const summary = deleteCredential(tool, credentialId);
|
|
157
|
+
res.json({ tool, summary });
|
|
158
|
+
} catch (error) {
|
|
159
|
+
res.status(error.statusCode || 500).json({ error: error.message });
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
module.exports = router;
|
|
@@ -16,6 +16,7 @@ const {
|
|
|
16
16
|
getCurrentProxyPort
|
|
17
17
|
} = require('../services/opencode-settings-manager');
|
|
18
18
|
const { getChannels, getEnabledChannels, applyChannelToSettings } = require('../services/opencode-channels');
|
|
19
|
+
const { clearNativeOAuth } = require('../services/native-oauth-adapters');
|
|
19
20
|
const { getSchedulerState } = require('../services/channel-scheduler');
|
|
20
21
|
const { PATHS, ensureStorageDirMigrated } = require('../../config/paths');
|
|
21
22
|
const fs = require('fs');
|
|
@@ -42,6 +43,24 @@ function selectLatestEnabledChannel(channels) {
|
|
|
42
43
|
}, enabledChannels[0]);
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
function resolveActiveChannel(channels, activeChannelId = null) {
|
|
47
|
+
if (!Array.isArray(channels) || channels.length === 0) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (activeChannelId) {
|
|
52
|
+
const matched = channels.find(channel => channel.id === activeChannelId);
|
|
53
|
+
if (matched) {
|
|
54
|
+
return matched;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return selectLatestEnabledChannel(channels)
|
|
59
|
+
|| channels.find(channel => channel.enabled !== false)
|
|
60
|
+
|| channels[0]
|
|
61
|
+
|| null;
|
|
62
|
+
}
|
|
63
|
+
|
|
45
64
|
// 保存激活渠道ID
|
|
46
65
|
function saveActiveChannelId(channelId) {
|
|
47
66
|
ensureStorageDirMigrated();
|
|
@@ -84,7 +103,7 @@ router.get('/status', (req, res) => {
|
|
|
84
103
|
const proxyStatus = getOpenCodeProxyStatus();
|
|
85
104
|
const { channels } = getChannels();
|
|
86
105
|
const enabledChannels = channels.filter(ch => ch.enabled !== false);
|
|
87
|
-
const activeChannel =
|
|
106
|
+
const activeChannel = resolveActiveChannel(channels, loadActiveChannelId());
|
|
88
107
|
const configStatus = {
|
|
89
108
|
isProxyConfig: isProxyConfig(),
|
|
90
109
|
configExists: configExists(),
|
|
@@ -176,6 +195,7 @@ router.post('/start', async (req, res) => {
|
|
|
176
195
|
});
|
|
177
196
|
|
|
178
197
|
const activeModel = currentChannel.model || currentChannel.speedTestModel || null;
|
|
198
|
+
clearNativeOAuth('opencode');
|
|
179
199
|
setProxyConfig(proxyResult.port, { channels: channelPayloads, model: activeModel });
|
|
180
200
|
|
|
181
201
|
// 5. 广播状态更新
|
|
@@ -199,17 +219,9 @@ router.post('/start', async (req, res) => {
|
|
|
199
219
|
// 停止代理
|
|
200
220
|
router.post('/stop', async (req, res) => {
|
|
201
221
|
try {
|
|
202
|
-
// 1. 获取当前激活渠道(优先使用启动动态切换时记录的渠道ID)
|
|
203
222
|
const { channels } = getChannels();
|
|
204
223
|
const activeChannelId = loadActiveChannelId();
|
|
205
|
-
|
|
206
|
-
if (!activeChannel && activeChannelId) {
|
|
207
|
-
activeChannel = channels.find(ch => ch.id === activeChannelId);
|
|
208
|
-
}
|
|
209
|
-
if (!activeChannel) {
|
|
210
|
-
const enabledChannels = channels.filter(ch => ch.enabled !== false);
|
|
211
|
-
activeChannel = enabledChannels[0] || channels[0] || null;
|
|
212
|
-
}
|
|
224
|
+
const activeChannel = resolveActiveChannel(channels, activeChannelId);
|
|
213
225
|
|
|
214
226
|
// 2. 停止代理服务器
|
|
215
227
|
const proxyResult = await stopOpenCodeProxyServer();
|
|
@@ -57,7 +57,8 @@ router.get('/', (req, res) => {
|
|
|
57
57
|
router.get('/market', async (req, res) => {
|
|
58
58
|
try {
|
|
59
59
|
const { platform, service } = getPluginsService(req);
|
|
60
|
-
const
|
|
60
|
+
const forceRefresh = req.query.refresh === '1';
|
|
61
|
+
const plugins = await service.getMarketPlugins(forceRefresh);
|
|
61
62
|
|
|
62
63
|
res.json({
|
|
63
64
|
success: true,
|