coding-tool-x 3.4.4 → 3.4.6
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/dist/web/assets/{Analytics-_Byi9M6y.js → Analytics-0PgPv5qO.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-DIwosdtG.js → ConfigTemplates-pBGoYbCP.js} +1 -1
- package/dist/web/assets/{Home-DdNMuQ9c.js → Home-BRN882om.js} +1 -1
- package/dist/web/assets/{PluginManager-iuY24cnW.js → PluginManager-am97Huts.js} +1 -1
- package/dist/web/assets/{ProjectList-DSkMulzL.js → ProjectList-CXS9KJN1.js} +1 -1
- package/dist/web/assets/{SessionList-B6pGquIr.js → SessionList-BZyrzH7J.js} +1 -1
- package/dist/web/assets/{SkillManager-CHtQX5r8.js → SkillManager-p1CI0tYa.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-gNPs-VaI.js → WorkspaceManager-CUPvLoba.js} +1 -1
- package/dist/web/assets/index-B4Wl3JfR.js +2 -0
- package/dist/web/assets/{index-pMqqe9ei.css → index-Bgt_oqoE.css} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +2 -2
- package/src/server/api/claude-hooks.js +1 -0
- package/src/server/api/codex-channels.js +26 -0
- package/src/server/api/oauth-credentials.js +23 -1
- package/src/server/api/opencode-proxy.js +0 -2
- package/src/server/api/plugins.js +161 -14
- package/src/server/api/skills.js +62 -7
- package/src/server/codex-proxy-server.js +10 -2
- package/src/server/gemini-proxy-server.js +10 -2
- package/src/server/opencode-proxy-server.js +10 -2
- package/src/server/proxy-server.js +10 -2
- package/src/server/services/codex-channels.js +64 -21
- package/src/server/services/codex-env-manager.js +44 -28
- package/src/server/services/native-oauth-adapters.js +94 -10
- package/src/server/services/oauth-credentials-service.js +44 -2
- package/src/server/services/opencode-channels.js +0 -2
- package/src/server/services/plugins-service.js +1060 -235
- package/src/server/services/proxy-runtime.js +129 -5
- package/src/server/services/server-shutdown.js +79 -0
- package/src/server/services/skill-service.js +142 -17
- package/dist/web/assets/index-DGjGCo37.js +0 -2
|
@@ -22,6 +22,7 @@ const { persistProxyRequestSnapshot, loadClaudeRequestTemplate } = require('./se
|
|
|
22
22
|
const { probeModelAvailability, fetchModelsFromProvider } = require('./services/model-detector');
|
|
23
23
|
const { publishUsageLog, publishFailureLog } = require('./services/proxy-log-helper');
|
|
24
24
|
const { redirectModel, resolveTargetUrl } = require('./services/base/proxy-utils');
|
|
25
|
+
const { attachServerShutdownHandling, expediteServerShutdown } = require('./services/server-shutdown');
|
|
25
26
|
|
|
26
27
|
let proxyServer = null;
|
|
27
28
|
let proxyApp = null;
|
|
@@ -4650,6 +4651,7 @@ async function startOpenCodeProxyServer(options = {}) {
|
|
|
4650
4651
|
|
|
4651
4652
|
// 启动服务器
|
|
4652
4653
|
proxyServer = http.createServer(proxyApp);
|
|
4654
|
+
attachServerShutdownHandling(proxyServer);
|
|
4653
4655
|
|
|
4654
4656
|
return new Promise((resolve, reject) => {
|
|
4655
4657
|
proxyServer.listen(port, '127.0.0.1', () => {
|
|
@@ -4692,8 +4694,13 @@ async function stopOpenCodeProxyServer(options = {}) {
|
|
|
4692
4694
|
|
|
4693
4695
|
requestMetadata.clear();
|
|
4694
4696
|
|
|
4697
|
+
const shutdownTimer = expediteServerShutdown(proxyServer);
|
|
4698
|
+
|
|
4695
4699
|
return new Promise((resolve) => {
|
|
4696
4700
|
proxyServer.close(() => {
|
|
4701
|
+
if (shutdownTimer) {
|
|
4702
|
+
clearTimeout(shutdownTimer);
|
|
4703
|
+
}
|
|
4697
4704
|
console.log('OpenCode proxy server stopped');
|
|
4698
4705
|
|
|
4699
4706
|
// 清除代理启动时间(仅当明确要求时)
|
|
@@ -4713,8 +4720,9 @@ async function stopOpenCodeProxyServer(options = {}) {
|
|
|
4713
4720
|
// 获取代理服务器状态
|
|
4714
4721
|
function getOpenCodeProxyStatus() {
|
|
4715
4722
|
const config = loadConfig();
|
|
4716
|
-
const
|
|
4717
|
-
const
|
|
4723
|
+
const allowRecovery = !!proxyServer;
|
|
4724
|
+
const startTime = getProxyStartTime('opencode', { allowRecovery });
|
|
4725
|
+
const runtime = getProxyRuntime('opencode', { allowRecovery });
|
|
4718
4726
|
|
|
4719
4727
|
return {
|
|
4720
4728
|
running: !!proxyServer,
|
|
@@ -20,6 +20,7 @@ const { getEffectiveApiKey } = require('./services/channels');
|
|
|
20
20
|
const { persistProxyRequestSnapshot, persistClaudeRequestTemplate } = require('./services/request-logger');
|
|
21
21
|
const { publishUsageLog, publishFailureLog } = require('./services/proxy-log-helper');
|
|
22
22
|
const { redirectModel } = require('./services/base/proxy-utils');
|
|
23
|
+
const { attachServerShutdownHandling, expediteServerShutdown } = require('./services/server-shutdown');
|
|
23
24
|
|
|
24
25
|
let proxyServer = null;
|
|
25
26
|
let proxyApp = null;
|
|
@@ -541,6 +542,7 @@ async function startProxyServer(options = {}) {
|
|
|
541
542
|
});
|
|
542
543
|
|
|
543
544
|
proxyServer = http.createServer(proxyApp);
|
|
545
|
+
attachServerShutdownHandling(proxyServer);
|
|
544
546
|
|
|
545
547
|
return new Promise((resolve, reject) => {
|
|
546
548
|
proxyServer.listen(port, '127.0.0.1', () => {
|
|
@@ -580,8 +582,13 @@ async function stopProxyServer(options = {}) {
|
|
|
580
582
|
|
|
581
583
|
requestMetadata.clear();
|
|
582
584
|
|
|
585
|
+
const shutdownTimer = expediteServerShutdown(proxyServer);
|
|
586
|
+
|
|
583
587
|
return new Promise((resolve) => {
|
|
584
588
|
proxyServer.close(() => {
|
|
589
|
+
if (shutdownTimer) {
|
|
590
|
+
clearTimeout(shutdownTimer);
|
|
591
|
+
}
|
|
585
592
|
console.log('[OK] Proxy server stopped');
|
|
586
593
|
if (clearStartTime) {
|
|
587
594
|
clearProxyStartTime('claude');
|
|
@@ -599,8 +606,9 @@ async function stopProxyServer(options = {}) {
|
|
|
599
606
|
// 获取代理服务器状态
|
|
600
607
|
function getProxyStatus() {
|
|
601
608
|
const config = loadConfig();
|
|
602
|
-
const
|
|
603
|
-
const
|
|
609
|
+
const allowRecovery = !!proxyServer;
|
|
610
|
+
const startTime = getProxyStartTime('claude', { allowRecovery });
|
|
611
|
+
const runtime = getProxyRuntime('claude', { allowRecovery });
|
|
604
612
|
|
|
605
613
|
return {
|
|
606
614
|
running: !!proxyServer,
|
|
@@ -5,27 +5,46 @@ const toml = require('toml');
|
|
|
5
5
|
const tomlStringify = require('@iarna/toml').stringify;
|
|
6
6
|
const { PATHS } = require('../../config/paths');
|
|
7
7
|
const { getCodexDir } = require('./codex-config');
|
|
8
|
-
const { isProxyConfig } = require('./codex-settings-manager');
|
|
8
|
+
const { isProxyConfig, readConfig } = require('./codex-settings-manager');
|
|
9
9
|
const { clearNativeOAuth } = require('./native-oauth-adapters');
|
|
10
10
|
const { syncCodexUserEnvironment } = require('./codex-env-manager');
|
|
11
11
|
const BaseChannelService = require('./base/base-channel-service');
|
|
12
12
|
|
|
13
|
-
const
|
|
13
|
+
const CODEX_MANAGED_ENV_KEY = 'CC_PROXY_KEY';
|
|
14
14
|
const CODEX_PROXY_ENV_VALUE = 'PROXY_KEY';
|
|
15
15
|
|
|
16
16
|
// ── Codex 特有工具函数 ──
|
|
17
17
|
|
|
18
|
-
function
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
function resolveCurrentManagedChannel(channels = []) {
|
|
19
|
+
const allChannels = Array.isArray(channels) ? channels : [];
|
|
20
|
+
let currentProvider = '';
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
currentProvider = String(readConfig()?.model_provider || '').trim();
|
|
24
|
+
} catch (err) {
|
|
25
|
+
currentProvider = '';
|
|
21
26
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
|
|
28
|
+
if (currentProvider && currentProvider !== 'cc-proxy') {
|
|
29
|
+
const matched = allChannels.find(ch => ch.providerKey === currentProvider);
|
|
30
|
+
if (matched) {
|
|
31
|
+
return matched;
|
|
26
32
|
}
|
|
27
33
|
}
|
|
28
|
-
|
|
34
|
+
|
|
35
|
+
return allChannels.find(ch => ch.enabled !== false) || null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function buildManagedCodexEnvMap(channels = [], { includeProxyKey = false, activeChannel = null } = {}) {
|
|
39
|
+
if (includeProxyKey) {
|
|
40
|
+
return { [CODEX_MANAGED_ENV_KEY]: CODEX_PROXY_ENV_VALUE };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const targetChannel = activeChannel || resolveCurrentManagedChannel(channels);
|
|
44
|
+
if (targetChannel?.apiKey) {
|
|
45
|
+
return { [CODEX_MANAGED_ENV_KEY]: targetChannel.apiKey };
|
|
46
|
+
}
|
|
47
|
+
return {};
|
|
29
48
|
}
|
|
30
49
|
|
|
31
50
|
function syncAllChannelEnvVars() {
|
|
@@ -34,7 +53,8 @@ function syncAllChannelEnvVars() {
|
|
|
34
53
|
const data = svc.loadChannels();
|
|
35
54
|
const proxyRunning = isProxyConfig();
|
|
36
55
|
const envMap = buildManagedCodexEnvMap(data.channels, {
|
|
37
|
-
includeProxyKey: proxyRunning
|
|
56
|
+
includeProxyKey: proxyRunning,
|
|
57
|
+
activeChannel: proxyRunning ? null : resolveCurrentManagedChannel(data.channels)
|
|
38
58
|
});
|
|
39
59
|
syncCodexUserEnvironment(envMap, { replace: true });
|
|
40
60
|
} catch (err) {
|
|
@@ -87,7 +107,7 @@ function writeCodexConfigForMultiChannel(channels) {
|
|
|
87
107
|
name: ch.name,
|
|
88
108
|
base_url: ch.baseUrl,
|
|
89
109
|
wire_api: ch.wireApi || 'responses',
|
|
90
|
-
env_key:
|
|
110
|
+
env_key: CODEX_MANAGED_ENV_KEY,
|
|
91
111
|
requires_openai_auth: ch.requiresOpenaiAuth !== false
|
|
92
112
|
};
|
|
93
113
|
if (ch.queryParams && Object.keys(ch.queryParams).length > 0) {
|
|
@@ -121,7 +141,7 @@ class CodexChannelService extends BaseChannelService {
|
|
|
121
141
|
_applyDefaults(channel) {
|
|
122
142
|
const ch = super._applyDefaults(channel);
|
|
123
143
|
ch.providerKey = ch.providerKey || '';
|
|
124
|
-
ch.envKey =
|
|
144
|
+
ch.envKey = CODEX_MANAGED_ENV_KEY;
|
|
125
145
|
ch.wireApi = ch.wireApi || 'responses';
|
|
126
146
|
ch.model = ch.model || '';
|
|
127
147
|
ch.speedTestModel = ch.speedTestModel || null;
|
|
@@ -143,19 +163,38 @@ class CodexChannelService extends BaseChannelService {
|
|
|
143
163
|
}
|
|
144
164
|
|
|
145
165
|
_onAfterCreate(_channel, _allChannels) {
|
|
166
|
+
if (_channel.enabled !== false && !isProxyConfig()) {
|
|
167
|
+
this._applyToNativeSettings(_channel);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
146
170
|
syncAllChannelEnvVars();
|
|
147
|
-
// 注意:不再自动写入 config.toml,只在开启代理控制时才同步
|
|
148
171
|
}
|
|
149
172
|
|
|
150
|
-
_onAfterUpdate(_old, _next,
|
|
173
|
+
_onAfterUpdate(_old, _next, allChannels) {
|
|
174
|
+
if (!isProxyConfig()) {
|
|
175
|
+
if (_old.enabled === false && _next.enabled !== false) {
|
|
176
|
+
this._applyToNativeSettings(_next);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const activeChannel = resolveCurrentManagedChannel(allChannels);
|
|
180
|
+
if (_next.enabled !== false && activeChannel?.id === _next.id) {
|
|
181
|
+
this._applyToNativeSettings(_next);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
151
185
|
syncAllChannelEnvVars();
|
|
152
|
-
// 注意:不再自动写入 config.toml,只在开启代理控制时才同步
|
|
153
186
|
}
|
|
154
187
|
|
|
155
|
-
_onAfterDelete(_channel,
|
|
188
|
+
_onAfterDelete(_channel, allChannels) {
|
|
189
|
+
if (!isProxyConfig()) {
|
|
190
|
+
const activeChannel = resolveCurrentManagedChannel(allChannels);
|
|
191
|
+
if (activeChannel && activeChannel.enabled !== false) {
|
|
192
|
+
this._applyToNativeSettings(activeChannel);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
156
196
|
clearNativeOAuth('codex');
|
|
157
197
|
syncAllChannelEnvVars();
|
|
158
|
-
// 注意:不再自动写入 config.toml,只在开启代理控制时才同步
|
|
159
198
|
}
|
|
160
199
|
|
|
161
200
|
_applyToNativeSettings(channel) {
|
|
@@ -184,7 +223,7 @@ class CodexChannelService extends BaseChannelService {
|
|
|
184
223
|
name: channel.name,
|
|
185
224
|
base_url: channel.baseUrl,
|
|
186
225
|
wire_api: channel.wireApi || 'responses',
|
|
187
|
-
env_key:
|
|
226
|
+
env_key: CODEX_MANAGED_ENV_KEY,
|
|
188
227
|
requires_openai_auth: channel.requiresOpenaiAuth !== false
|
|
189
228
|
};
|
|
190
229
|
|
|
@@ -215,10 +254,9 @@ const service = getServiceInstance();
|
|
|
215
254
|
function getChannels() { return service.getChannels(); }
|
|
216
255
|
function getEnabledChannels() { return service.getEnabledChannels(); }
|
|
217
256
|
function createChannel(name, providerKey, baseUrl, apiKey, wireApi, extraConfig = {}) {
|
|
218
|
-
const envKey = extraConfig.envKey || `${providerKey.toUpperCase()}_API_KEY`;
|
|
219
257
|
return service.createChannel({
|
|
220
258
|
name, providerKey, baseUrl, apiKey, wireApi,
|
|
221
|
-
envKey,
|
|
259
|
+
envKey: CODEX_MANAGED_ENV_KEY,
|
|
222
260
|
...extraConfig,
|
|
223
261
|
});
|
|
224
262
|
}
|
|
@@ -251,4 +289,9 @@ module.exports = {
|
|
|
251
289
|
applyChannelToSettings,
|
|
252
290
|
getEffectiveApiKey,
|
|
253
291
|
disableAllChannels,
|
|
292
|
+
_test: {
|
|
293
|
+
buildManagedCodexEnvMap,
|
|
294
|
+
CODEX_MANAGED_ENV_KEY,
|
|
295
|
+
resolveCurrentManagedChannel
|
|
296
|
+
}
|
|
254
297
|
};
|
|
@@ -32,6 +32,40 @@ function powershellQuote(value) {
|
|
|
32
32
|
return `'${String(value).replace(/'/g, "''")}'`;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
function buildWindowsSettingChangeScript() {
|
|
36
|
+
return [
|
|
37
|
+
'Add-Type -Namespace Win32 -Name NativeMethods -MemberDefinition @"',
|
|
38
|
+
'[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]',
|
|
39
|
+
'public static extern IntPtr SendMessageTimeout(',
|
|
40
|
+
' IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam,',
|
|
41
|
+
' uint fuFlags, uint uTimeout, out UIntPtr lpdwResult);',
|
|
42
|
+
'"@',
|
|
43
|
+
'$HWND_BROADCAST = [IntPtr]0xffff',
|
|
44
|
+
'$WM_SETTINGCHANGE = 0x1a',
|
|
45
|
+
'$SMTO_ABORTIFHUNG = 0x0002',
|
|
46
|
+
'$result = [UIntPtr]::Zero',
|
|
47
|
+
'[Win32.NativeMethods]::SendMessageTimeout($HWND_BROADCAST, $WM_SETTINGCHANGE,',
|
|
48
|
+
' [UIntPtr]::Zero, "Environment", $SMTO_ABORTIFHUNG, 5000, [ref]$result) | Out-Null'
|
|
49
|
+
].join('\n');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function buildWindowsEnvBatchScript(operations = [], { includeSettingChangeBroadcast = true } = {}) {
|
|
53
|
+
const normalizedOperations = Array.isArray(operations) ? operations.filter(Boolean) : [];
|
|
54
|
+
const lines = normalizedOperations.map((operation) => {
|
|
55
|
+
const key = powershellQuote(operation.key || '');
|
|
56
|
+
if (operation.remove) {
|
|
57
|
+
return `[Environment]::SetEnvironmentVariable(${key}, $null, 'User')`;
|
|
58
|
+
}
|
|
59
|
+
return `[Environment]::SetEnvironmentVariable(${key}, ${powershellQuote(operation.value || '')}, 'User')`;
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (includeSettingChangeBroadcast && lines.length > 0) {
|
|
63
|
+
lines.push(buildWindowsSettingChangeScript());
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return lines.join('\n');
|
|
67
|
+
}
|
|
68
|
+
|
|
35
69
|
function buildHomeRelativeShellPath(filePath, homeDir) {
|
|
36
70
|
const normalizedHome = path.resolve(homeDir);
|
|
37
71
|
const normalizedFilePath = path.resolve(filePath);
|
|
@@ -341,48 +375,28 @@ function runLaunchctlCommand(args, execSync) {
|
|
|
341
375
|
}
|
|
342
376
|
|
|
343
377
|
function broadcastWindowsSettingChange(execSync) {
|
|
344
|
-
|
|
345
|
-
'Add-Type -Namespace Win32 -Name NativeMethods -MemberDefinition @"',
|
|
346
|
-
'[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]',
|
|
347
|
-
'public static extern IntPtr SendMessageTimeout(',
|
|
348
|
-
' IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam,',
|
|
349
|
-
' uint fuFlags, uint uTimeout, out UIntPtr lpdwResult);',
|
|
350
|
-
'"@',
|
|
351
|
-
'$HWND_BROADCAST = [IntPtr]0xffff',
|
|
352
|
-
'$WM_SETTINGCHANGE = 0x1a',
|
|
353
|
-
'$SMTO_ABORTIFHUNG = 0x0002',
|
|
354
|
-
'$result = [UIntPtr]::Zero',
|
|
355
|
-
'[Win32.NativeMethods]::SendMessageTimeout($HWND_BROADCAST, $WM_SETTINGCHANGE,',
|
|
356
|
-
' [UIntPtr]::Zero, "Environment", $SMTO_ABORTIFHUNG, 5000, [ref]$result) | Out-Null'
|
|
357
|
-
].join('\n');
|
|
358
|
-
runWindowsEnvCommand(script, execSync);
|
|
378
|
+
runWindowsEnvCommand(buildWindowsSettingChangeScript(), execSync);
|
|
359
379
|
}
|
|
360
380
|
|
|
361
381
|
function syncWindowsEnvironment(nextValues, previousState, options) {
|
|
362
382
|
const { stateFilePath, execSync } = options;
|
|
363
383
|
const nextKeys = Object.keys(nextValues).sort();
|
|
364
384
|
const previousValues = previousState.values || {};
|
|
365
|
-
|
|
385
|
+
const operations = [];
|
|
366
386
|
|
|
367
387
|
for (const [key, value] of Object.entries(nextValues)) {
|
|
368
388
|
if (previousValues[key] === value) continue;
|
|
369
|
-
|
|
370
|
-
changed = true;
|
|
389
|
+
operations.push({ key, value });
|
|
371
390
|
}
|
|
372
391
|
|
|
373
392
|
for (const key of Object.keys(previousValues)) {
|
|
374
393
|
if (Object.prototype.hasOwnProperty.call(nextValues, key)) continue;
|
|
375
|
-
|
|
376
|
-
changed = true;
|
|
394
|
+
operations.push({ key, remove: true });
|
|
377
395
|
}
|
|
378
396
|
|
|
379
|
-
|
|
397
|
+
const changed = operations.length > 0;
|
|
380
398
|
if (changed) {
|
|
381
|
-
|
|
382
|
-
broadcastWindowsSettingChange(execSync);
|
|
383
|
-
} catch {
|
|
384
|
-
// 广播失败不影响主流程,环境变量已写入注册表
|
|
385
|
-
}
|
|
399
|
+
runWindowsEnvCommand(buildWindowsEnvBatchScript(operations), execSync);
|
|
386
400
|
}
|
|
387
401
|
|
|
388
402
|
if (nextKeys.length > 0) {
|
|
@@ -427,14 +441,14 @@ function runWindowsEnvCommand(script, execSync) {
|
|
|
427
441
|
|
|
428
442
|
function setWindowsUserEnv(key, value, execSync) {
|
|
429
443
|
runWindowsEnvCommand(
|
|
430
|
-
|
|
444
|
+
buildWindowsEnvBatchScript([{ key, value }], { includeSettingChangeBroadcast: false }),
|
|
431
445
|
execSync
|
|
432
446
|
);
|
|
433
447
|
}
|
|
434
448
|
|
|
435
449
|
function removeWindowsUserEnv(key, execSync) {
|
|
436
450
|
runWindowsEnvCommand(
|
|
437
|
-
|
|
451
|
+
buildWindowsEnvBatchScript([{ key, remove: true }], { includeSettingChangeBroadcast: false }),
|
|
438
452
|
execSync
|
|
439
453
|
);
|
|
440
454
|
}
|
|
@@ -473,8 +487,10 @@ module.exports = {
|
|
|
473
487
|
syncCodexUserEnvironment,
|
|
474
488
|
_test: {
|
|
475
489
|
broadcastWindowsSettingChange,
|
|
490
|
+
buildWindowsEnvBatchScript,
|
|
476
491
|
buildHomeRelativeShellPath,
|
|
477
492
|
buildNextEnvValues,
|
|
493
|
+
buildWindowsSettingChangeScript,
|
|
478
494
|
buildSourceSnippet,
|
|
479
495
|
getPosixProfileCandidates,
|
|
480
496
|
readState,
|
|
@@ -219,7 +219,7 @@ function inspectClaudeState() {
|
|
|
219
219
|
|
|
220
220
|
return {
|
|
221
221
|
tool: 'claude',
|
|
222
|
-
mode: proxyStatus.running ? 'proxy' : (
|
|
222
|
+
mode: proxyStatus.running ? 'proxy' : (channelConfigured ? 'channel' : (nativeOAuth ? 'oauth' : 'idle')),
|
|
223
223
|
proxyRunning: proxyStatus.running,
|
|
224
224
|
oauthPresent: Boolean(nativeOAuth),
|
|
225
225
|
channelConfigured,
|
|
@@ -395,7 +395,7 @@ function inspectCodexState() {
|
|
|
395
395
|
|
|
396
396
|
return {
|
|
397
397
|
tool: 'codex',
|
|
398
|
-
mode: proxyStatus.running ? 'proxy' : (
|
|
398
|
+
mode: proxyStatus.running ? 'proxy' : (channelConfigured ? 'channel' : (nativeOAuth ? 'oauth' : 'idle')),
|
|
399
399
|
proxyRunning: proxyStatus.running,
|
|
400
400
|
oauthPresent: Boolean(nativeOAuth),
|
|
401
401
|
channelConfigured,
|
|
@@ -619,7 +619,7 @@ function inspectGeminiState() {
|
|
|
619
619
|
|
|
620
620
|
return {
|
|
621
621
|
tool: 'gemini',
|
|
622
|
-
mode: proxyStatus.running ? 'proxy' : (
|
|
622
|
+
mode: proxyStatus.running ? 'proxy' : (channelConfigured ? 'channel' : (nativeOAuth ? 'oauth' : 'idle')),
|
|
623
623
|
proxyRunning: proxyStatus.running,
|
|
624
624
|
oauthPresent: Boolean(nativeOAuth),
|
|
625
625
|
channelConfigured,
|
|
@@ -737,10 +737,67 @@ function clearOpenCodeOAuth() {
|
|
|
737
737
|
writeJsonFile(NATIVE_PATHS.opencode.auth, payload);
|
|
738
738
|
}
|
|
739
739
|
|
|
740
|
-
function
|
|
741
|
-
|
|
742
|
-
|
|
740
|
+
function disableOpenCodeOAuthCredential(credential = {}) {
|
|
741
|
+
const providerId = String(credential.providerId || '').trim();
|
|
742
|
+
const accessToken = String(credential.accessToken || credential.primaryToken || '').trim();
|
|
743
|
+
const payload = readJsonFile(NATIVE_PATHS.opencode.auth, {});
|
|
744
|
+
if (!payload || typeof payload !== 'object') {
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
Object.keys(payload).forEach((key) => {
|
|
749
|
+
const target = payload[key];
|
|
750
|
+
if (!target || target.type !== 'oauth') {
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
const providerMatched = providerId && key === providerId;
|
|
755
|
+
const tokenMatched = accessToken && String(target.access || '').trim() === accessToken;
|
|
756
|
+
if (providerMatched || tokenMatched) {
|
|
757
|
+
delete payload[key];
|
|
758
|
+
}
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
if (Object.keys(payload).length === 0) {
|
|
762
|
+
removeFileIfExists(NATIVE_PATHS.opencode.auth);
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
writeJsonFile(NATIVE_PATHS.opencode.auth, payload);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
function isManagedOpenCodeProvider(provider) {
|
|
770
|
+
if (!provider || typeof provider !== 'object') {
|
|
771
|
+
return false;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
if (provider.__ctx_managed__ === true) {
|
|
775
|
+
return true;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
const apiKey = String(provider?.options?.apiKey || '').trim();
|
|
779
|
+
const baseUrl = String(provider?.options?.baseURL || '').trim();
|
|
780
|
+
return apiKey === 'PROXY_KEY' && (baseUrl.includes('127.0.0.1') || baseUrl.includes('localhost'));
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
function clearOpenCodeManagedModelSelection(config) {
|
|
784
|
+
const modelRef = String(config?.model || '').trim();
|
|
785
|
+
if (!modelRef || !modelRef.includes('/')) {
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
const providerId = modelRef.split('/')[0].trim();
|
|
790
|
+
if (!providerId) {
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
const provider = config?.provider?.[providerId];
|
|
795
|
+
if (isManagedOpenCodeProvider(provider)) {
|
|
796
|
+
delete config.model;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
743
799
|
|
|
800
|
+
function applyOpenCodeOAuth(credential) {
|
|
744
801
|
const providerId = String(credential.providerId || 'openai').trim() || 'openai';
|
|
745
802
|
const payload = readJsonFile(NATIVE_PATHS.opencode.auth, {});
|
|
746
803
|
payload[providerId] = {
|
|
@@ -758,9 +815,12 @@ function applyOpenCodeOAuth(credential) {
|
|
|
758
815
|
? opencodeSettingsManager.readConfig(configPath)
|
|
759
816
|
: {};
|
|
760
817
|
config.provider = config.provider && typeof config.provider === 'object' ? config.provider : {};
|
|
761
|
-
|
|
762
|
-
config.provider[providerId]
|
|
763
|
-
|
|
818
|
+
config.provider[providerId] = config.provider[providerId] && typeof config.provider[providerId] === 'object'
|
|
819
|
+
? config.provider[providerId]
|
|
820
|
+
: {};
|
|
821
|
+
// Preserve existing ctx-managed API providers for OpenCode coexistence, but
|
|
822
|
+
// drop the active managed selection so OAuth-backed providers become available.
|
|
823
|
+
clearOpenCodeManagedModelSelection(config);
|
|
764
824
|
opencodeSettingsManager.writeConfig(configPath, config);
|
|
765
825
|
|
|
766
826
|
return { storage: 'auth-file' };
|
|
@@ -787,7 +847,11 @@ function inspectOpenCodeState() {
|
|
|
787
847
|
|
|
788
848
|
return {
|
|
789
849
|
tool: 'opencode',
|
|
790
|
-
mode: proxyStatus.running
|
|
850
|
+
mode: proxyStatus.running
|
|
851
|
+
? 'proxy'
|
|
852
|
+
: (nativeOAuth && channelConfigured
|
|
853
|
+
? 'mixed'
|
|
854
|
+
: (nativeOAuth ? 'oauth' : (channelConfigured ? 'channel' : 'idle'))),
|
|
791
855
|
proxyRunning: proxyStatus.running,
|
|
792
856
|
oauthPresent: Boolean(nativeOAuth),
|
|
793
857
|
channelConfigured,
|
|
@@ -865,6 +929,25 @@ function clearNativeOAuth(tool) {
|
|
|
865
929
|
}
|
|
866
930
|
}
|
|
867
931
|
|
|
932
|
+
function disableNativeOAuthCredential(tool, credential = {}) {
|
|
933
|
+
switch (tool) {
|
|
934
|
+
case 'claude':
|
|
935
|
+
clearClaudeOAuth();
|
|
936
|
+
return;
|
|
937
|
+
case 'codex':
|
|
938
|
+
clearCodexOAuth();
|
|
939
|
+
return;
|
|
940
|
+
case 'gemini':
|
|
941
|
+
clearGeminiOAuth();
|
|
942
|
+
return;
|
|
943
|
+
case 'opencode':
|
|
944
|
+
disableOpenCodeOAuthCredential(credential);
|
|
945
|
+
return;
|
|
946
|
+
default:
|
|
947
|
+
throw new Error(`Unsupported OAuth tool: ${tool}`);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
868
951
|
function applyOAuthCredential(tool, credential) {
|
|
869
952
|
switch (tool) {
|
|
870
953
|
case 'claude':
|
|
@@ -887,5 +970,6 @@ module.exports = {
|
|
|
887
970
|
readNativeOAuth,
|
|
888
971
|
readAllNativeOAuth,
|
|
889
972
|
clearNativeOAuth,
|
|
973
|
+
disableNativeOAuthCredential,
|
|
890
974
|
applyOAuthCredential
|
|
891
975
|
};
|
|
@@ -12,6 +12,7 @@ const {
|
|
|
12
12
|
inspectTool,
|
|
13
13
|
readAllNativeOAuth,
|
|
14
14
|
clearNativeOAuth,
|
|
15
|
+
disableNativeOAuthCredential,
|
|
15
16
|
applyOAuthCredential
|
|
16
17
|
} = require('./native-oauth-adapters');
|
|
17
18
|
const { maskToken, decodeJwtPayload, removeFileIfExists } = require('./oauth-utils');
|
|
@@ -300,6 +301,23 @@ function sanitizeCredential(entry, defaultCredentialId) {
|
|
|
300
301
|
};
|
|
301
302
|
}
|
|
302
303
|
|
|
304
|
+
function sanitizeNativeCredential(entry = {}) {
|
|
305
|
+
const primaryToken = entry.primaryToken
|
|
306
|
+
|| entry.accessToken
|
|
307
|
+
|| entry.token
|
|
308
|
+
|| '';
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
providerId: entry.providerId || '',
|
|
312
|
+
accountId: entry.accountId || '',
|
|
313
|
+
accountEmail: entry.accountEmail || '',
|
|
314
|
+
expiresAt: entry.expiresAt || null,
|
|
315
|
+
lastRefresh: entry.lastRefresh || null,
|
|
316
|
+
storage: entry.storage || '',
|
|
317
|
+
tokenPreview: maskToken(primaryToken)
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
303
321
|
function sanitizeToolSummary(tool, toolStore) {
|
|
304
322
|
const credentials = (toolStore.credentials || [])
|
|
305
323
|
.map((entry) => sanitizeCredential(entry, toolStore.defaultCredentialId))
|
|
@@ -309,11 +327,16 @@ function sanitizeToolSummary(tool, toolStore) {
|
|
|
309
327
|
if (aTime !== bTime) return bTime - aTime;
|
|
310
328
|
return (b.createdAt || 0) - (a.createdAt || 0);
|
|
311
329
|
});
|
|
330
|
+
const nativeState = inspectTool(tool);
|
|
331
|
+
const nativeCredentials = readAllNativeOAuth(tool).map((entry) => sanitizeNativeCredential(entry));
|
|
312
332
|
return {
|
|
313
333
|
tool,
|
|
314
334
|
defaultCredentialId: toolStore.defaultCredentialId || null,
|
|
315
335
|
credentials,
|
|
316
|
-
nativeState:
|
|
336
|
+
nativeState: {
|
|
337
|
+
...nativeState,
|
|
338
|
+
nativeCredentials
|
|
339
|
+
}
|
|
317
340
|
};
|
|
318
341
|
}
|
|
319
342
|
|
|
@@ -612,7 +635,9 @@ async function applyStoredCredential(tool, credentialId) {
|
|
|
612
635
|
const entry = findStoredCredential(tool, credentialId);
|
|
613
636
|
const proxyStopped = await stopProxyIfRunning(tool);
|
|
614
637
|
cleanupManagedArtifacts(tool);
|
|
615
|
-
|
|
638
|
+
if (tool !== 'opencode') {
|
|
639
|
+
disableAllChannelsForTool(tool);
|
|
640
|
+
}
|
|
616
641
|
applyOAuthCredential(tool, entry.secrets);
|
|
617
642
|
|
|
618
643
|
// 记录最近使用时间
|
|
@@ -631,6 +656,22 @@ async function applyStoredCredential(tool, credentialId) {
|
|
|
631
656
|
};
|
|
632
657
|
}
|
|
633
658
|
|
|
659
|
+
function disableStoredCredential(tool, credentialId) {
|
|
660
|
+
assertSupportedTool(tool);
|
|
661
|
+
const entry = findStoredCredential(tool, credentialId);
|
|
662
|
+
disableNativeOAuthCredential(tool, {
|
|
663
|
+
...(entry.secrets || {}),
|
|
664
|
+
providerId: entry.providerId || entry.secrets?.providerId || '',
|
|
665
|
+
accountId: entry.accountId || entry.secrets?.accountId || ''
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
return {
|
|
669
|
+
credential: sanitizeCredential(entry, readStore().tools[tool]?.defaultCredentialId || null),
|
|
670
|
+
toolSummary: getToolSummary(tool),
|
|
671
|
+
nativeState: inspectTool(tool)
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
|
|
634
675
|
function clearNativeOAuthState(tool) {
|
|
635
676
|
assertSupportedTool(tool);
|
|
636
677
|
clearNativeOAuth(tool);
|
|
@@ -791,6 +832,7 @@ module.exports = {
|
|
|
791
832
|
setDefaultCredential,
|
|
792
833
|
deleteCredential,
|
|
793
834
|
applyStoredCredential,
|
|
835
|
+
disableStoredCredential,
|
|
794
836
|
clearNativeOAuthState,
|
|
795
837
|
fetchCredentialUsage
|
|
796
838
|
};
|
|
@@ -2,7 +2,6 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const crypto = require('crypto');
|
|
4
4
|
const { PATHS } = require('../../config/paths');
|
|
5
|
-
const { clearNativeOAuth } = require('./native-oauth-adapters');
|
|
6
5
|
const { setChannelConfig } = require('./opencode-settings-manager');
|
|
7
6
|
const { normalizeGatewaySourceType } = require('./base/proxy-utils');
|
|
8
7
|
|
|
@@ -251,7 +250,6 @@ function applyChannelToSettings(channelId) {
|
|
|
251
250
|
});
|
|
252
251
|
saveChannels(data);
|
|
253
252
|
|
|
254
|
-
clearNativeOAuth('opencode');
|
|
255
253
|
setChannelConfig(channel);
|
|
256
254
|
|
|
257
255
|
return channel;
|