coding-tool-x 3.4.0 → 3.4.2
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-DEjfL5Jx.js → Analytics-CbGxotgz.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-DkRL_-tf.js → ConfigTemplates-oP6nrFEb.js} +1 -1
- package/dist/web/assets/{Home-CF-L640I.js → Home-DMntmEvh.js} +1 -1
- package/dist/web/assets/{PluginManager-BzNYTdNB.js → PluginManager-BUC_c7nH.js} +1 -1
- package/dist/web/assets/{ProjectList-C0-JgHMM.js → ProjectList-CW8J49n7.js} +1 -1
- package/dist/web/assets/{SessionList-CkZUdX5N.js → SessionList-7lYnF92v.js} +1 -1
- package/dist/web/assets/{SkillManager-Cak0-4d4.js → SkillManager-Cs08216i.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-CGDJzwEr.js → WorkspaceManager-CY-oGtyB.js} +1 -1
- package/dist/web/assets/{index-Dz7v9OM0.css → index-5qy5NMIP.css} +1 -1
- package/dist/web/assets/index-ClCqKpvX.js +2 -0
- package/dist/web/index.html +2 -2
- package/package.json +6 -2
- package/src/commands/doctor.js +2 -2
- package/src/commands/resume.js +1 -0
- package/src/commands/update.js +2 -1
- package/src/plugins/plugin-installer.js +1 -0
- package/src/server/api/claude-hooks.js +2 -3
- package/src/server/api/workspaces.js +2 -1
- package/src/server/codex-proxy-server.js +4 -92
- package/src/server/gemini-proxy-server.js +5 -28
- package/src/server/opencode-proxy-server.js +3 -93
- package/src/server/proxy-server.js +2 -57
- package/src/server/services/base/base-channel-service.js +247 -0
- package/src/server/services/base/proxy-utils.js +152 -0
- package/src/server/services/channel-health.js +30 -19
- package/src/server/services/channels.js +125 -293
- package/src/server/services/codex-channels.js +148 -513
- package/src/server/services/codex-env-manager.js +81 -21
- package/src/server/services/codex-settings-manager.js +20 -5
- package/src/server/services/gemini-channels.js +2 -7
- package/src/server/services/mcp-client.js +2 -1
- package/src/server/services/notification-hooks.js +9 -8
- package/src/server/services/oauth-credentials-service.js +12 -2
- package/src/server/services/opencode-channels.js +7 -9
- package/src/server/services/opencode-sessions.js +4 -2
- package/src/server/services/plugins-service.js +2 -1
- package/src/server/services/repo-scanner-base.js +1 -0
- package/src/server/services/skill-service.js +4 -2
- package/src/server/services/workspace-service.js +1 -0
- package/src/utils/port-helper.js +5 -5
- package/dist/web/assets/index-D_WItvHE.js +0 -2
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const BaseChannelService = require('./base/base-channel-service');
|
|
3
4
|
const { isProxyConfig } = require('./settings-manager');
|
|
4
5
|
const { PATHS, NATIVE_PATHS } = require('../../config/paths');
|
|
5
6
|
const { clearNativeOAuth } = require('./native-oauth-adapters');
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
const dir = path.dirname(PATHS.channels.claude);
|
|
9
|
-
if (!fs.existsSync(dir)) {
|
|
10
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
11
|
-
}
|
|
12
|
-
return PATHS.channels.claude;
|
|
13
|
-
}
|
|
8
|
+
// ── Claude 特有工具函数 ──
|
|
14
9
|
|
|
15
10
|
function getActiveChannelIdPath() {
|
|
16
11
|
const dir = path.dirname(PATHS.activeChannel.claude);
|
|
@@ -43,298 +38,23 @@ function loadActiveChannelId() {
|
|
|
43
38
|
return null;
|
|
44
39
|
}
|
|
45
40
|
|
|
46
|
-
let channelsCache = null;
|
|
47
|
-
let channelsCacheInitialized = false;
|
|
48
|
-
const DEFAULT_CHANNELS = { channels: [] };
|
|
49
|
-
|
|
50
|
-
function normalizeNumber(value, defaultValue, max = null) {
|
|
51
|
-
const num = Number(value);
|
|
52
|
-
if (!Number.isFinite(num) || num <= 0) {
|
|
53
|
-
return defaultValue;
|
|
54
|
-
}
|
|
55
|
-
if (max !== null && num > max) {
|
|
56
|
-
return max;
|
|
57
|
-
}
|
|
58
|
-
return num;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function normalizeGatewaySourceType(value, fallback = 'claude') {
|
|
62
|
-
const normalized = String(value || '').trim().toLowerCase();
|
|
63
|
-
if (normalized === 'claude') return 'claude';
|
|
64
|
-
if (normalized === 'codex') return 'codex';
|
|
65
|
-
if (normalized === 'gemini') return 'gemini';
|
|
66
|
-
return fallback;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
41
|
function extractApiKeyFromHelper(apiKeyHelper) {
|
|
70
42
|
if (typeof apiKeyHelper !== 'string' || !apiKeyHelper.trim()) {
|
|
71
43
|
return '';
|
|
72
44
|
}
|
|
73
|
-
|
|
74
45
|
const helper = apiKeyHelper.trim();
|
|
75
46
|
let match = helper.match(/^echo\s+["']([^"']+)["']$/);
|
|
76
|
-
if (match && match[1])
|
|
77
|
-
return match[1];
|
|
78
|
-
}
|
|
79
|
-
|
|
47
|
+
if (match && match[1]) return match[1];
|
|
80
48
|
match = helper.match(/^printf\s+["'][^"']*["']\s+["']([^"']+)["']$/);
|
|
81
|
-
if (match && match[1])
|
|
82
|
-
return match[1];
|
|
83
|
-
}
|
|
84
|
-
|
|
49
|
+
if (match && match[1]) return match[1];
|
|
85
50
|
return '';
|
|
86
51
|
}
|
|
87
52
|
|
|
88
53
|
function buildApiKeyHelperCommand() {
|
|
89
|
-
|
|
90
|
-
return 'printf "%s" "${ANTHROPIC_AUTH_TOKEN:-${ANTHROPIC_API_KEY:-}}"';
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function applyChannelDefaults(channel) {
|
|
94
|
-
const normalized = { ...channel };
|
|
95
|
-
if (normalized.enabled === undefined) {
|
|
96
|
-
normalized.enabled = true;
|
|
97
|
-
} else {
|
|
98
|
-
normalized.enabled = !!normalized.enabled;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
normalized.weight = normalizeNumber(normalized.weight, 1, 100);
|
|
102
|
-
|
|
103
|
-
if (normalized.maxConcurrency === undefined ||
|
|
104
|
-
normalized.maxConcurrency === null ||
|
|
105
|
-
normalized.maxConcurrency === 0) {
|
|
106
|
-
normalized.maxConcurrency = null;
|
|
107
|
-
} else {
|
|
108
|
-
normalized.maxConcurrency = normalizeNumber(normalized.maxConcurrency, 1, 100);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
normalized.gatewaySourceType = normalizeGatewaySourceType(normalized.gatewaySourceType, 'claude');
|
|
112
|
-
|
|
113
|
-
return normalized;
|
|
54
|
+
return 'echo \'ctx-managed\'';
|
|
114
55
|
}
|
|
115
56
|
|
|
116
|
-
|
|
117
|
-
const filePath = getChannelsFilePath();
|
|
118
|
-
try {
|
|
119
|
-
if (fs.existsSync(filePath)) {
|
|
120
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
121
|
-
const data = JSON.parse(content);
|
|
122
|
-
data.channels = (data.channels || []).map(applyChannelDefaults);
|
|
123
|
-
return data;
|
|
124
|
-
}
|
|
125
|
-
} catch (error) {
|
|
126
|
-
console.error('Error loading channels:', error);
|
|
127
|
-
}
|
|
128
|
-
return { ...DEFAULT_CHANNELS };
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function initializeChannelsCache() {
|
|
132
|
-
if (channelsCacheInitialized) return;
|
|
133
|
-
channelsCache = readChannelsFromFile();
|
|
134
|
-
channelsCacheInitialized = true;
|
|
135
|
-
|
|
136
|
-
try {
|
|
137
|
-
const filePath = getChannelsFilePath();
|
|
138
|
-
fs.watchFile(filePath, { persistent: false }, () => {
|
|
139
|
-
channelsCache = readChannelsFromFile();
|
|
140
|
-
});
|
|
141
|
-
} catch (err) {
|
|
142
|
-
console.error('Failed to watch channels file:', err);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function loadChannels() {
|
|
147
|
-
if (!channelsCacheInitialized) {
|
|
148
|
-
initializeChannelsCache();
|
|
149
|
-
}
|
|
150
|
-
return JSON.parse(JSON.stringify(channelsCache));
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function saveChannels(data) {
|
|
154
|
-
const filePath = getChannelsFilePath();
|
|
155
|
-
const payload = {
|
|
156
|
-
...data,
|
|
157
|
-
channels: (data.channels || []).map(applyChannelDefaults)
|
|
158
|
-
};
|
|
159
|
-
fs.writeFileSync(filePath, JSON.stringify(payload, null, 2), 'utf8');
|
|
160
|
-
channelsCache = JSON.parse(JSON.stringify(payload));
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function getCurrentSettings() {
|
|
164
|
-
try {
|
|
165
|
-
const settingsPath = getClaudeSettingsPath();
|
|
166
|
-
if (!fs.existsSync(settingsPath)) {
|
|
167
|
-
return null;
|
|
168
|
-
}
|
|
169
|
-
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
170
|
-
const nativeOAuth = require('./native-oauth-adapters').readNativeOAuth('claude');
|
|
171
|
-
|
|
172
|
-
let baseUrl = settings.env?.ANTHROPIC_BASE_URL || '';
|
|
173
|
-
let apiKey = settings.env?.ANTHROPIC_API_KEY || '';
|
|
174
|
-
if (!apiKey && !nativeOAuth) {
|
|
175
|
-
apiKey = settings.env?.ANTHROPIC_AUTH_TOKEN || '';
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (!apiKey && settings.apiKeyHelper) {
|
|
179
|
-
apiKey = extractApiKeyFromHelper(settings.apiKeyHelper);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (!baseUrl && !apiKey) {
|
|
183
|
-
return null;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return { baseUrl, apiKey };
|
|
187
|
-
} catch (error) {
|
|
188
|
-
console.error('Error reading current settings:', error);
|
|
189
|
-
return null;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function getBestChannelForRestore() {
|
|
194
|
-
const data = loadChannels();
|
|
195
|
-
const enabledChannels = data.channels.filter(ch => ch.enabled !== false);
|
|
196
|
-
|
|
197
|
-
if (enabledChannels.length === 0) {
|
|
198
|
-
return data.channels[0];
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
enabledChannels.sort((a, b) => (b.weight || 1) - (a.weight || 1));
|
|
202
|
-
return enabledChannels[0];
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function getAllChannels() {
|
|
206
|
-
const data = loadChannels();
|
|
207
|
-
return data.channels;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function getCurrentChannel() {
|
|
211
|
-
const channels = getAllChannels();
|
|
212
|
-
if (!Array.isArray(channels) || channels.length === 0) {
|
|
213
|
-
return null;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const activeChannelId = loadActiveChannelId();
|
|
217
|
-
if (activeChannelId) {
|
|
218
|
-
const matched = channels.find(ch => ch.id === activeChannelId);
|
|
219
|
-
if (matched) {
|
|
220
|
-
return matched;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
return channels.find(ch => ch.enabled !== false) || channels[0];
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function createChannel(name, baseUrl, apiKey, websiteUrl, extraConfig = {}) {
|
|
228
|
-
const data = loadChannels();
|
|
229
|
-
const newChannel = applyChannelDefaults({
|
|
230
|
-
id: `channel-${Date.now()}`,
|
|
231
|
-
name,
|
|
232
|
-
baseUrl,
|
|
233
|
-
apiKey,
|
|
234
|
-
createdAt: Date.now(),
|
|
235
|
-
websiteUrl: websiteUrl || undefined,
|
|
236
|
-
enabled: extraConfig.enabled !== undefined ? !!extraConfig.enabled : true,
|
|
237
|
-
weight: extraConfig.weight,
|
|
238
|
-
maxConcurrency: extraConfig.maxConcurrency,
|
|
239
|
-
presetId: extraConfig.presetId || 'official',
|
|
240
|
-
modelConfig: extraConfig.modelConfig || null,
|
|
241
|
-
modelRedirects: extraConfig.modelRedirects || [],
|
|
242
|
-
proxyUrl: extraConfig.proxyUrl || '',
|
|
243
|
-
speedTestModel: extraConfig.speedTestModel || null,
|
|
244
|
-
gatewaySourceType: normalizeGatewaySourceType(extraConfig.gatewaySourceType, 'claude')
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
data.channels.push(newChannel);
|
|
248
|
-
saveChannels(data);
|
|
249
|
-
return newChannel;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function updateChannel(id, updates) {
|
|
253
|
-
const data = loadChannels();
|
|
254
|
-
const index = data.channels.findIndex(ch => ch.id === id);
|
|
255
|
-
|
|
256
|
-
if (index === -1) {
|
|
257
|
-
throw new Error('Channel not found');
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Store old channel data before updates
|
|
261
|
-
const oldChannel = { ...data.channels[index] };
|
|
262
|
-
|
|
263
|
-
const merged = { ...data.channels[index], ...updates };
|
|
264
|
-
const nextChannel = applyChannelDefaults({
|
|
265
|
-
...merged,
|
|
266
|
-
weight: merged.weight,
|
|
267
|
-
maxConcurrency: merged.maxConcurrency,
|
|
268
|
-
enabled: merged.enabled,
|
|
269
|
-
presetId: merged.presetId,
|
|
270
|
-
modelConfig: merged.modelConfig,
|
|
271
|
-
modelRedirects: merged.modelRedirects || [],
|
|
272
|
-
proxyUrl: merged.proxyUrl,
|
|
273
|
-
speedTestModel: merged.speedTestModel,
|
|
274
|
-
gatewaySourceType: normalizeGatewaySourceType(merged.gatewaySourceType, 'claude'),
|
|
275
|
-
updatedAt: Date.now()
|
|
276
|
-
});
|
|
277
|
-
data.channels[index] = nextChannel;
|
|
278
|
-
|
|
279
|
-
// Get proxy status
|
|
280
|
-
const { getProxyStatus } = require('../proxy-server');
|
|
281
|
-
const proxyStatus = getProxyStatus();
|
|
282
|
-
const isProxyRunning = proxyStatus.running;
|
|
283
|
-
|
|
284
|
-
// Single-channel enforcement: enabling a channel disables all others ONLY when proxy is OFF
|
|
285
|
-
// When proxy is ON (dynamic switching), multiple channels can be enabled simultaneously
|
|
286
|
-
if (!isProxyRunning && nextChannel.enabled && !oldChannel.enabled) {
|
|
287
|
-
data.channels.forEach((ch, i) => {
|
|
288
|
-
if (i !== index && ch.enabled) {
|
|
289
|
-
ch.enabled = false;
|
|
290
|
-
}
|
|
291
|
-
});
|
|
292
|
-
console.log(`[Single-channel mode] Enabled "${nextChannel.name}", disabled all others`);
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
saveChannels(data);
|
|
296
|
-
|
|
297
|
-
// Sync settings.json only when proxy is OFF.
|
|
298
|
-
// In dynamic switching mode, defer local config writes until proxy stop.
|
|
299
|
-
if (!isProxyRunning && nextChannel.enabled) {
|
|
300
|
-
console.log(`[Settings-sync] Channel "${nextChannel.name}" enabled, syncing settings.json...`);
|
|
301
|
-
updateClaudeSettingsWithModelConfig(nextChannel);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
return data.channels[index];
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
async function deleteChannel(id) {
|
|
308
|
-
const data = loadChannels();
|
|
309
|
-
const index = data.channels.findIndex(ch => ch.id === id);
|
|
310
|
-
|
|
311
|
-
if (index === -1) {
|
|
312
|
-
throw new Error('Channel not found');
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
data.channels.splice(index, 1);
|
|
316
|
-
saveChannels(data);
|
|
317
|
-
|
|
318
|
-
return { success: true };
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
function applyChannelToSettings(id) {
|
|
322
|
-
const data = loadChannels();
|
|
323
|
-
const channel = data.channels.find(ch => ch.id === id);
|
|
324
|
-
|
|
325
|
-
if (!channel) {
|
|
326
|
-
throw new Error('Channel not found');
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// In single-channel mode, only this channel should be enabled
|
|
330
|
-
data.channels.forEach(ch => {
|
|
331
|
-
ch.enabled = ch.id === id;
|
|
332
|
-
});
|
|
333
|
-
saveChannels(data);
|
|
334
|
-
updateClaudeSettingsWithModelConfig(channel);
|
|
335
|
-
|
|
336
|
-
return channel;
|
|
337
|
-
}
|
|
57
|
+
// ── Claude 原生设置写入 ──
|
|
338
58
|
|
|
339
59
|
function updateClaudeSettingsWithModelConfig(channel) {
|
|
340
60
|
clearNativeOAuth('claude');
|
|
@@ -386,7 +106,6 @@ function updateClaudeSettingsWithModelConfig(channel) {
|
|
|
386
106
|
}
|
|
387
107
|
|
|
388
108
|
settings.apiKeyHelper = buildApiKeyHelperCommand();
|
|
389
|
-
|
|
390
109
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
391
110
|
}
|
|
392
111
|
|
|
@@ -415,18 +134,131 @@ function updateClaudeSettings(baseUrl, apiKey) {
|
|
|
415
134
|
}
|
|
416
135
|
|
|
417
136
|
settings.apiKeyHelper = buildApiKeyHelperCommand();
|
|
418
|
-
|
|
419
137
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
420
138
|
}
|
|
421
139
|
|
|
140
|
+
// ── ClaudeChannelService ──
|
|
141
|
+
|
|
142
|
+
class ClaudeChannelService extends BaseChannelService {
|
|
143
|
+
constructor() {
|
|
144
|
+
super({
|
|
145
|
+
platform: 'claude',
|
|
146
|
+
channelsFilePath: PATHS.channels.claude,
|
|
147
|
+
defaultGatewaySource: 'claude',
|
|
148
|
+
isProxyRunning: () => isProxyConfig(),
|
|
149
|
+
});
|
|
150
|
+
// Claude 特有:文件监听缓存
|
|
151
|
+
this._cache = null;
|
|
152
|
+
this._cacheInitialized = false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
_generateId() {
|
|
156
|
+
return `channel-${Date.now()}`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Claude 使用缓存 + fs.watchFile
|
|
160
|
+
loadChannels() {
|
|
161
|
+
if (this._cacheInitialized && this._cache) {
|
|
162
|
+
return { channels: this._cache.channels.map(ch => this._applyDefaults(ch)) };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const data = super.loadChannels();
|
|
166
|
+
this._cache = data;
|
|
167
|
+
this._cacheInitialized = true;
|
|
168
|
+
|
|
169
|
+
// 设置文件监听
|
|
170
|
+
try {
|
|
171
|
+
fs.watchFile(this.channelsFilePath, { interval: 2000 }, () => {
|
|
172
|
+
try {
|
|
173
|
+
this._cache = null;
|
|
174
|
+
this._cacheInitialized = false;
|
|
175
|
+
} catch (_) {}
|
|
176
|
+
});
|
|
177
|
+
} catch (_) {}
|
|
178
|
+
|
|
179
|
+
return data;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
saveChannels(data) {
|
|
183
|
+
super.saveChannels(data);
|
|
184
|
+
this._cache = data;
|
|
185
|
+
this._cacheInitialized = true;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
_applyToNativeSettings(channel) {
|
|
189
|
+
updateClaudeSettingsWithModelConfig(channel);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
getEffectiveApiKey(channel) {
|
|
193
|
+
return channel?.apiKey || null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ── 单例 + 兼容导出 ──
|
|
198
|
+
|
|
199
|
+
const service = new ClaudeChannelService();
|
|
200
|
+
|
|
201
|
+
function getAllChannels() {
|
|
202
|
+
const data = service.loadChannels();
|
|
203
|
+
return data.channels;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function getCurrentChannel() {
|
|
207
|
+
const channels = getAllChannels();
|
|
208
|
+
const activeId = loadActiveChannelId();
|
|
209
|
+
if (activeId) {
|
|
210
|
+
const active = channels.find(ch => ch.id === activeId);
|
|
211
|
+
if (active) return active;
|
|
212
|
+
}
|
|
213
|
+
return channels.find(ch => ch.enabled !== false) || channels[0] || null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function getCurrentSettings() {
|
|
217
|
+
const channel = getCurrentChannel();
|
|
218
|
+
if (!channel) return null;
|
|
219
|
+
return {
|
|
220
|
+
baseUrl: channel.baseUrl,
|
|
221
|
+
apiKey: channel.apiKey,
|
|
222
|
+
channelName: channel.name,
|
|
223
|
+
channelId: channel.id,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function getBestChannelForRestore() {
|
|
228
|
+
const channels = getAllChannels();
|
|
229
|
+
const enabled = channels.filter(ch => ch.enabled !== false);
|
|
230
|
+
if (enabled.length > 0) return enabled[0];
|
|
231
|
+
return channels[0] || null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function createChannel(name, baseUrl, apiKey, websiteUrl, extraConfig) {
|
|
235
|
+
return service.createChannel({
|
|
236
|
+
name,
|
|
237
|
+
baseUrl,
|
|
238
|
+
apiKey,
|
|
239
|
+
websiteUrl,
|
|
240
|
+
...extraConfig,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function updateChannel(id, updates) {
|
|
245
|
+
return service.updateChannel(id, updates);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function deleteChannel(id) {
|
|
249
|
+
return service.deleteChannel(id);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function applyChannelToSettings(id) {
|
|
253
|
+
return service.applyChannelToSettings(id);
|
|
254
|
+
}
|
|
255
|
+
|
|
422
256
|
function getEffectiveApiKey(channel) {
|
|
423
|
-
return channel
|
|
257
|
+
return service.getEffectiveApiKey(channel);
|
|
424
258
|
}
|
|
425
259
|
|
|
426
260
|
function disableAllChannels() {
|
|
427
|
-
|
|
428
|
-
data.channels.forEach(ch => { ch.enabled = false; });
|
|
429
|
-
saveChannels(data);
|
|
261
|
+
return service.disableAllChannels();
|
|
430
262
|
}
|
|
431
263
|
|
|
432
264
|
module.exports = {
|
|
@@ -441,5 +273,5 @@ module.exports = {
|
|
|
441
273
|
updateClaudeSettings,
|
|
442
274
|
updateClaudeSettingsWithModelConfig,
|
|
443
275
|
getEffectiveApiKey,
|
|
444
|
-
disableAllChannels
|
|
276
|
+
disableAllChannels,
|
|
445
277
|
};
|