coding-tool-x 3.8.9 → 3.9.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/README.md +59 -387
- package/dist/web/assets/AgentsDrawer-DooV5Qhx.js +1 -0
- package/dist/web/assets/Analytics-BXRwqC-P.js +1 -0
- package/dist/web/assets/AssetPathField-B-LHvOxq.js +1 -0
- package/dist/web/assets/{ChatHistoryDrawer-BTmQ1aQr.js → ChatHistoryDrawer-CoHlGAxb.js} +1 -1
- package/dist/web/assets/{CollapseItem-CwqJk3bE.js → CollapseItem-kJkiHxFl.js} +1 -1
- package/dist/web/assets/CommandsDrawer-DLQrikcw.js +1 -0
- package/dist/web/assets/ConfigExportDrawer-BItwo0FQ.js +1 -0
- package/dist/web/assets/{ConfigTemplates-gyERg9iC.js → ConfigTemplates-R3YzzBT3.js} +1 -1
- package/dist/web/assets/{ConfigTemplatesDrawer-B17RqFi4.js → ConfigTemplatesDrawer-C_nPKrhx.js} +1 -1
- package/dist/web/assets/ConfigTemplatesPanel-BiwrNyYN.js +1 -0
- package/dist/web/assets/{Divider-DsdbkHrG.js → Divider-BLLhMTSt.js} +1 -1
- package/dist/web/assets/FavoritesDrawer-BAJ-AOpN.js +1 -0
- package/dist/web/assets/GatewayConvertDrawer-Av3yzwhP.js +1 -0
- package/dist/web/assets/Home-B01aL_SR.js +1 -0
- package/dist/web/assets/{MarkdownEditor-9TTwTS-U.js → MarkdownEditor-D75GGt5X.js} +1 -1
- package/dist/web/assets/McpDrawer-xNzJtT4X.js +1 -0
- package/dist/web/assets/McpFormDrawer-B240mJ5y.js +1 -0
- package/dist/web/assets/{PluginManager-BuRcWuBj.js → PluginManager-CgLjBdlr.js} +1 -1
- package/dist/web/assets/{PluginsDrawer-_zZENaQn.js → PluginsDrawer-DvJdwMXx.js} +1 -1
- package/dist/web/assets/PluginsPanel-kX14E_DT.js +1 -0
- package/dist/web/assets/ProjectList-CRtc3Hhx.js +1 -0
- package/dist/web/assets/PromptsDrawer-CvT9dA2N.js +1 -0
- package/dist/web/assets/RecentSessionsDrawer-DM1gf584.js +1 -0
- package/dist/web/assets/SessionCard-cZR2V4Br.js +1 -0
- package/dist/web/assets/SessionList-D7-P4Zme.js +1 -0
- package/dist/web/assets/SettingsDrawer-BFzaxbgC.css +1 -0
- package/dist/web/assets/SettingsDrawer-gW8kh-S8.js +1 -0
- package/dist/web/assets/{SkillManager-S698Hns8.js → SkillManager-D0BlI7CG.js} +1 -1
- package/dist/web/assets/{SkillsDrawer-Ms4lPCow.js → SkillsDrawer-BMB6uzqP.js} +1 -1
- package/dist/web/assets/SkillsPanel-BzR828De.js +1 -0
- package/dist/web/assets/Upload-3fNyOQrx.js +1 -0
- package/dist/web/assets/{WorkspaceDrawer-L7HJRc6u.js → WorkspaceDrawer-DvtBA58Q.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-DsoNg2z8.js → WorkspaceManager-BW5KtsHO.js} +1 -1
- package/dist/web/assets/WorkspacePanel-BC_xpAEZ.js +1 -0
- package/dist/web/assets/client-BgcNg4Bt.js +1 -0
- package/dist/web/assets/{headers-B5JWGn3v.js → headers-DbowkmZJ.js} +1 -1
- package/dist/web/assets/{icons-DmFe4b5j.js → icons-B-AzZoeE.js} +1 -1
- package/dist/web/assets/index-BFqnddoI.js +2 -0
- package/dist/web/assets/index-Bn8BUnwP.css +1 -0
- package/dist/web/assets/{light-0eavxNvt.js → light-C2X7hegj.js} +1 -1
- package/dist/web/assets/platforms-DrzAtnjY.js +1 -0
- package/dist/web/assets/platforms-mqqe_82d.js +1 -0
- package/dist/web/assets/project-config-BVt5s-pH.js +1 -0
- package/dist/web/assets/sessions-53-Fr68H.js +1 -0
- package/dist/web/assets/{sessions-D4yQtfKb.js → sessions-CaWUaysM.js} +1 -1
- package/dist/web/assets/use-message-DBsCm7FJ.js +1 -0
- package/dist/web/assets/{useResponsiveDrawer-V_onl9NV.js → useResponsiveDrawer-Ckgj760z.js} +1 -1
- package/dist/web/assets/{workspaces-mjyPVGS8.js → workspaces-DQuZd4qw.js} +1 -1
- package/dist/web/index.html +8 -8
- package/docs/analytics.png +0 -0
- package/docs/home.png +0 -0
- package/package.json +4 -2
- package/src/commands/export-config.js +17 -211
- package/src/config/model-metadata.json +180 -16
- package/src/platforms/channel-auth-service.js +1 -1
- package/src/platforms/commands-service.js +28 -0
- package/src/platforms/driver-registry.js +1 -0
- package/src/platforms/drivers/claude/channels-implementation.js +79 -90
- package/src/platforms/drivers/claude/native-config-implementation.js +3 -4
- package/src/platforms/drivers/codex/channels-implementation.js +34 -49
- package/src/platforms/drivers/codex/native-config-implementation.js +3 -51
- package/src/platforms/drivers/dsh/api-operations.js +1 -1
- package/src/platforms/drivers/dsh/channels-implementation.js +28 -8
- package/src/platforms/drivers/dsh/channels.js +0 -18
- package/src/platforms/drivers/dsh/common.js +7 -0
- package/src/platforms/drivers/dsh/native-config.js +7 -2
- package/src/platforms/drivers/gemini/channels-implementation.js +47 -55
- package/src/platforms/drivers/gemini/native-config-implementation.js +8 -20
- package/src/platforms/drivers/generic-native-config.js +20 -0
- package/src/platforms/drivers/native-config-snapshot.js +30 -4
- package/src/platforms/drivers/omp/channels-implementation.js +51 -17
- package/src/platforms/drivers/omp/native-config-implementation.js +10 -17
- package/src/platforms/drivers/opencode/channels-implementation.js +6 -0
- package/src/platforms/drivers/opencode/native-config-implementation.js +3 -67
- package/src/platforms/manifest-schema.js +35 -2
- package/src/platforms/manifests/claude.json +0 -8
- package/src/platforms/manifests/codex.json +0 -8
- package/src/platforms/manifests/dsh.json +6 -9
- package/src/platforms/manifests/gemini.json +0 -8
- package/src/platforms/manifests/omp.json +0 -8
- package/src/platforms/manifests/opencode.json +0 -8
- package/src/platforms/native-oauth-adapters.js +33 -33
- package/src/platforms/remote-notification-providers.js +5 -5
- package/src/server/api/settings.js +6 -5
- package/src/server/index.js +2 -0
- package/src/server/services/bounded-content.js +60 -0
- package/src/server/services/channel-balance.js +10 -4
- package/src/server/services/config-export-service.js +2 -2
- package/src/server/services/config-templates-service.js +5 -1
- package/src/server/services/plugins-service.js +162 -85
- package/src/server/services/session-history-maintenance-worker.js +71 -0
- package/src/server/services/session-history-maintenance.js +166 -0
- package/src/server/services/skill-service.js +23 -3
- package/src/server/services/speed-test.js +0 -72
- package/src/shared/channel-driver.js +16 -0
- package/src/shared/driver-factories/api.js +1 -0
- package/src/utils/native-config-patcher.js +474 -0
- package/dist/web/assets/AgentsDrawer-Cj5CJ4d9.js +0 -1
- package/dist/web/assets/Analytics-1doS-8uC.js +0 -1
- package/dist/web/assets/AssetPathField-C9TOPl7o.js +0 -1
- package/dist/web/assets/CommandsDrawer-CaZWDI_G.js +0 -1
- package/dist/web/assets/ConfigExportDrawer-C3vSZEsS.js +0 -1
- package/dist/web/assets/ConfigTemplatesPanel-DKIC_FqG.js +0 -1
- package/dist/web/assets/FavoritesDrawer-DaziQmBp.js +0 -1
- package/dist/web/assets/GatewayConvertDrawer-OMgWUUSc.js +0 -1
- package/dist/web/assets/Home-DOSUTfrE.js +0 -1
- package/dist/web/assets/McpDrawer-I8rl8lGH.js +0 -1
- package/dist/web/assets/McpFormDrawer-DEP3C7ut.js +0 -1
- package/dist/web/assets/PluginsPanel-Bs5qZ-TK.js +0 -1
- package/dist/web/assets/ProjectList-BSxuL2td.js +0 -1
- package/dist/web/assets/PromptsDrawer-CoZ-ZtsY.js +0 -1
- package/dist/web/assets/RecentSessionsDrawer-B_17aJBG.js +0 -1
- package/dist/web/assets/SessionCard-BL_x0d3t.js +0 -1
- package/dist/web/assets/SessionList-DhRYZ6vl.js +0 -1
- package/dist/web/assets/SettingsDrawer-Dw5NUgHC.css +0 -1
- package/dist/web/assets/SettingsDrawer-fYb0DU7X.js +0 -1
- package/dist/web/assets/SkillsPanel-rqnqAOGL.js +0 -1
- package/dist/web/assets/SpeedTestDrawer-Bbn38SfV.js +0 -1
- package/dist/web/assets/SpeedTestDrawer-Dc0WToJ_.css +0 -1
- package/dist/web/assets/Upload-CdyVHxCI.js +0 -1
- package/dist/web/assets/WorkspacePanel-Be8PVZah.js +0 -1
- package/dist/web/assets/client-UDwXBP9i.js +0 -1
- package/dist/web/assets/index-3Lp6z8TQ.css +0 -1
- package/dist/web/assets/index-BKlAWTH-.js +0 -2
- package/dist/web/assets/platforms-BmzzwnSw.js +0 -1
- package/dist/web/assets/platforms-DWXgmy2w.js +0 -1
- package/dist/web/assets/project-config-B2baPzYL.js +0 -1
- package/dist/web/assets/sessions-CC0j-KH_.js +0 -1
- package/dist/web/assets/use-message-DqCwefG6.js +0 -1
|
@@ -2,8 +2,8 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const crypto = require('crypto');
|
|
4
4
|
const toml = require('toml');
|
|
5
|
-
const tomlStringify = require('@iarna/toml').stringify;
|
|
6
5
|
const { PATHS } = require('../../../config/paths');
|
|
6
|
+
const { writeTomlFile } = require('../../../utils/native-config-patcher');
|
|
7
7
|
const { getCodexDir } = require('./config');
|
|
8
8
|
const { isProxyConfig, readConfig } = require('./native-config-implementation');
|
|
9
9
|
const { syncCodexUserEnvironment } = require('./env-manager');
|
|
@@ -175,21 +175,24 @@ function syncAllChannelEnvVars() {
|
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
function writeAnnotatedCodexConfig(configPath, config
|
|
179
|
-
|
|
180
|
-
if (comments.length > 0) {
|
|
181
|
-
tomlContent = comments.join('\n') + '\n\n' + tomlContent;
|
|
182
|
-
}
|
|
183
|
-
fs.writeFileSync(configPath, tomlContent, 'utf8');
|
|
178
|
+
function writeAnnotatedCodexConfig(configPath, config) {
|
|
179
|
+
writeTomlFile(configPath, config, { atomic: true });
|
|
184
180
|
}
|
|
185
181
|
|
|
186
|
-
function
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
182
|
+
function mergeCodexProviderConfig(existing = {}, channel = {}) {
|
|
183
|
+
const provider = {
|
|
184
|
+
...existing,
|
|
185
|
+
name: channel.name,
|
|
186
|
+
base_url: channel.baseUrl,
|
|
187
|
+
wire_api: channel.wireApi || 'responses',
|
|
188
|
+
env_key: CODEX_MANAGED_ENV_KEY,
|
|
189
|
+
requires_openai_auth: channelRequiresOpenaiAuth(channel)
|
|
190
|
+
};
|
|
191
|
+
delete provider.query_params;
|
|
192
|
+
if (channel.queryParams && Object.keys(channel.queryParams).length > 0) {
|
|
193
|
+
provider.query_params = channel.queryParams;
|
|
192
194
|
}
|
|
195
|
+
return provider;
|
|
193
196
|
}
|
|
194
197
|
|
|
195
198
|
function writeCodexConfigForMultiChannel(channels) {
|
|
@@ -201,8 +204,7 @@ function writeCodexConfigForMultiChannel(channels) {
|
|
|
201
204
|
try {
|
|
202
205
|
config = toml.parse(fs.readFileSync(configPath, 'utf8'));
|
|
203
206
|
} catch (err) {
|
|
204
|
-
|
|
205
|
-
config = {};
|
|
207
|
+
throw new Error(`Failed to parse existing config.toml: ${err.message}`);
|
|
206
208
|
}
|
|
207
209
|
}
|
|
208
210
|
|
|
@@ -229,26 +231,17 @@ function writeCodexConfigForMultiChannel(channels) {
|
|
|
229
231
|
config.model_provider = primary.providerKey;
|
|
230
232
|
|
|
231
233
|
for (const ch of enabledChannels) {
|
|
232
|
-
config.model_providers[ch.providerKey] =
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
env_key: CODEX_MANAGED_ENV_KEY,
|
|
237
|
-
requires_openai_auth: channelRequiresOpenaiAuth(ch)
|
|
238
|
-
};
|
|
239
|
-
if (ch.queryParams && Object.keys(ch.queryParams).length > 0) {
|
|
240
|
-
config.model_providers[ch.providerKey].query_params = ch.queryParams;
|
|
241
|
-
}
|
|
234
|
+
config.model_providers[ch.providerKey] = mergeCodexProviderConfig(
|
|
235
|
+
config.model_providers[ch.providerKey],
|
|
236
|
+
ch
|
|
237
|
+
);
|
|
242
238
|
}
|
|
243
239
|
} else {
|
|
244
240
|
clearManagedCodexConfig();
|
|
245
241
|
return;
|
|
246
242
|
}
|
|
247
243
|
|
|
248
|
-
writeAnnotatedCodexConfig(configPath, config
|
|
249
|
-
'# Codex Configuration',
|
|
250
|
-
'# Managed by Coding-Tool (multi-channel)'
|
|
251
|
-
]);
|
|
244
|
+
writeAnnotatedCodexConfig(configPath, config);
|
|
252
245
|
}
|
|
253
246
|
|
|
254
247
|
// ── CodexChannelService ──
|
|
@@ -315,6 +308,12 @@ class CodexChannelService extends BaseChannelService {
|
|
|
315
308
|
this._applyToNativeSettings(_next);
|
|
316
309
|
return;
|
|
317
310
|
}
|
|
311
|
+
if (_old.enabled !== false && _next.enabled === false) {
|
|
312
|
+
const activeChannel = resolveCurrentManagedChannel(allChannels);
|
|
313
|
+
if (activeChannel) this._applyToNativeSettings(activeChannel);
|
|
314
|
+
else clearManagedCodexConfig();
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
318
317
|
const activeChannel = resolveCurrentManagedChannel(allChannels);
|
|
319
318
|
if (_next.enabled !== false && activeChannel?.id === _next.id) {
|
|
320
319
|
this._applyToNativeSettings(_next);
|
|
@@ -347,7 +346,7 @@ class CodexChannelService extends BaseChannelService {
|
|
|
347
346
|
try {
|
|
348
347
|
config = toml.parse(fs.readFileSync(configPath, 'utf8'));
|
|
349
348
|
} catch (err) {
|
|
350
|
-
config
|
|
349
|
+
throw new Error(`Failed to parse existing config.toml: ${err.message}`);
|
|
351
350
|
}
|
|
352
351
|
}
|
|
353
352
|
|
|
@@ -356,26 +355,12 @@ class CodexChannelService extends BaseChannelService {
|
|
|
356
355
|
if (!config.model_providers || typeof config.model_providers !== 'object') {
|
|
357
356
|
config.model_providers = {};
|
|
358
357
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
name: channel.name,
|
|
364
|
-
base_url: channel.baseUrl,
|
|
365
|
-
wire_api: channel.wireApi || 'responses',
|
|
366
|
-
env_key: CODEX_MANAGED_ENV_KEY,
|
|
367
|
-
requires_openai_auth: channelRequiresOpenaiAuth(channel)
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
if (channel.queryParams && Object.keys(channel.queryParams).length > 0) {
|
|
371
|
-
config.model_providers[channel.providerKey].query_params = channel.queryParams;
|
|
372
|
-
}
|
|
358
|
+
config.model_providers[channel.providerKey] = mergeCodexProviderConfig(
|
|
359
|
+
config.model_providers[channel.providerKey],
|
|
360
|
+
channel
|
|
361
|
+
);
|
|
373
362
|
|
|
374
|
-
writeAnnotatedCodexConfig(configPath, config
|
|
375
|
-
'# Codex Configuration',
|
|
376
|
-
'# Managed by Coding-Tool',
|
|
377
|
-
`# Current provider: ${channel.name}`
|
|
378
|
-
]);
|
|
363
|
+
writeAnnotatedCodexConfig(configPath, config);
|
|
379
364
|
console.log(`[Codex Channels] Applied channel ${channel.name} to config.toml`);
|
|
380
365
|
syncAllChannelEnvVars();
|
|
381
366
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const toml = require('toml');
|
|
4
|
-
const
|
|
4
|
+
const { writeTomlFile } = require('../../../utils/native-config-patcher');
|
|
5
5
|
const { NATIVE_PATHS } = require('../../../config/paths');
|
|
6
6
|
const { syncCodexUserEnvironment } = require('./env-manager');
|
|
7
7
|
const DEFAULT_NATIVE_PATHS = NATIVE_PATHS.codex;
|
|
@@ -83,53 +83,10 @@ function readConfig() {
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
// 将配置对象转换为 TOML 字符串
|
|
87
|
-
function configToToml(config) {
|
|
88
|
-
let content = `# Codex Configuration
|
|
89
|
-
# Managed by Coding-Tool (Proxy Mode)
|
|
90
|
-
|
|
91
|
-
`;
|
|
92
|
-
|
|
93
|
-
// 写入顶级字段
|
|
94
|
-
for (const [key, value] of Object.entries(config)) {
|
|
95
|
-
if (key === 'model_providers') continue; // 稍后处理
|
|
96
|
-
if (typeof value === 'string') {
|
|
97
|
-
content += `${key} = "${value}"\n`;
|
|
98
|
-
} else if (typeof value === 'boolean') {
|
|
99
|
-
content += `${key} = ${value}\n`;
|
|
100
|
-
} else if (typeof value === 'number') {
|
|
101
|
-
content += `${key} = ${value}\n`;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
content += '\n';
|
|
106
|
-
|
|
107
|
-
// 写入 model_providers
|
|
108
|
-
if (config.model_providers) {
|
|
109
|
-
for (const [providerKey, providerConfig] of Object.entries(config.model_providers)) {
|
|
110
|
-
content += `[model_providers.${providerKey}]\n`;
|
|
111
|
-
for (const [key, value] of Object.entries(providerConfig)) {
|
|
112
|
-
if (typeof value === 'string') {
|
|
113
|
-
content += `${key} = "${value}"\n`;
|
|
114
|
-
} else if (typeof value === 'boolean') {
|
|
115
|
-
content += `${key} = ${value}\n`;
|
|
116
|
-
} else if (typeof value === 'number') {
|
|
117
|
-
content += `${key} = ${value}\n`;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
content += '\n';
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return content;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
86
|
// 写入 config.toml
|
|
128
87
|
function writeConfig(config) {
|
|
129
88
|
try {
|
|
130
|
-
|
|
131
|
-
const content = tomlStringify(safeConfig);
|
|
132
|
-
fs.writeFileSync(getConfigPath(), content, 'utf8');
|
|
89
|
+
writeTomlFile(getConfigPath(), config || {}, { atomic: true });
|
|
133
90
|
} catch (err) {
|
|
134
91
|
throw new Error('Failed to write config.toml: ' + err.message);
|
|
135
92
|
}
|
|
@@ -254,12 +211,7 @@ function setProxyConfig(proxyPort) {
|
|
|
254
211
|
}
|
|
255
212
|
|
|
256
213
|
// 读取当前配置(不存在时使用空配置)
|
|
257
|
-
|
|
258
|
-
try {
|
|
259
|
-
config = readConfig();
|
|
260
|
-
} catch (err) {
|
|
261
|
-
config = {};
|
|
262
|
-
}
|
|
214
|
+
const config = configExists() ? readConfig() : {};
|
|
263
215
|
|
|
264
216
|
// 设置 model_provider 为 proxy
|
|
265
217
|
config.model_provider = 'cc-proxy';
|
|
@@ -186,7 +186,7 @@ function createDriver(context = {}) {
|
|
|
186
186
|
|
|
187
187
|
// Keep the aggregate DSH API driver compatible with the manifest consistency
|
|
188
188
|
// contract. Actual descriptor requests are still dispatched by capability.
|
|
189
|
-
for (const operation of ['list', 'current', 'enabled', 'create', 'update', 'remove', 'applyToSettings', 'sync', 'order', 'models', 'probeModels', 'speedTest'
|
|
189
|
+
for (const operation of ['list', 'current', 'enabled', 'create', 'update', 'remove', 'applyToSettings', 'sync', 'order', 'models', 'probeModels', 'speedTest']) {
|
|
190
190
|
driver[operation] = (...args) => channels[operation](...args);
|
|
191
191
|
}
|
|
192
192
|
for (const operation of ['status', 'start', 'stop']) {
|
|
@@ -4,11 +4,12 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { getPlatformStatePath: resolveStatePath } = require('../../../config/paths');
|
|
6
6
|
const BaseChannelService = require('../../../shared/base-channel-service');
|
|
7
|
+
const { writeYamlFile } = require('../../../utils/native-config-patcher');
|
|
7
8
|
const {
|
|
8
9
|
resolvePaths,
|
|
9
10
|
readYamlFile,
|
|
10
11
|
writeAtomic,
|
|
11
|
-
|
|
12
|
+
DSH_YAML_SOURCE_TAGS,
|
|
12
13
|
isObject
|
|
13
14
|
} = require('./common');
|
|
14
15
|
|
|
@@ -202,7 +203,7 @@ class DshChannelService extends BaseChannelService {
|
|
|
202
203
|
}
|
|
203
204
|
|
|
204
205
|
_activeChannel(channels = []) {
|
|
205
|
-
return channels.find(channel => channel.enabled !== false) ||
|
|
206
|
+
return channels.find(channel => channel.enabled !== false) || null;
|
|
206
207
|
}
|
|
207
208
|
|
|
208
209
|
_onAfterCreate(channel) {
|
|
@@ -217,9 +218,8 @@ class DshChannelService extends BaseChannelService {
|
|
|
217
218
|
this._removeNativeProvider(oldChannel.providerKey);
|
|
218
219
|
}
|
|
219
220
|
const active = this._activeChannel(allChannels);
|
|
220
|
-
if (active
|
|
221
|
-
|
|
222
|
-
}
|
|
221
|
+
if (active) this._applyToNativeSettings(active);
|
|
222
|
+
else this._removeNativeProvider(nextChannel.providerKey);
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
_onAfterDelete(channel, allChannels) {
|
|
@@ -234,7 +234,11 @@ class DshChannelService extends BaseChannelService {
|
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
_writeNativeSettings(settings) {
|
|
237
|
-
|
|
237
|
+
writeYamlFile(this.nativePaths.settings, settings, {
|
|
238
|
+
atomic: true,
|
|
239
|
+
mode: 0o600,
|
|
240
|
+
customTags: DSH_YAML_SOURCE_TAGS
|
|
241
|
+
});
|
|
238
242
|
}
|
|
239
243
|
|
|
240
244
|
_removeNativeProvider(providerKey) {
|
|
@@ -246,6 +250,15 @@ class DshChannelService extends BaseChannelService {
|
|
|
246
250
|
if (!Object.prototype.hasOwnProperty.call(providers, key)) return;
|
|
247
251
|
delete providers[key];
|
|
248
252
|
settings['llm-pi-ai'] = { ...llm, providers };
|
|
253
|
+
const selected = isObject(settings['agent-default-model'])
|
|
254
|
+
? { ...settings['agent-default-model'] }
|
|
255
|
+
: null;
|
|
256
|
+
if (selected?.provider === key) {
|
|
257
|
+
delete selected.provider;
|
|
258
|
+
delete selected.model;
|
|
259
|
+
if (Object.keys(selected).length > 0) settings['agent-default-model'] = selected;
|
|
260
|
+
else delete settings['agent-default-model'];
|
|
261
|
+
}
|
|
249
262
|
this._writeNativeSettings(settings);
|
|
250
263
|
}
|
|
251
264
|
|
|
@@ -255,7 +268,11 @@ class DshChannelService extends BaseChannelService {
|
|
|
255
268
|
if (!isObject(credentials.refs)) credentials.refs = {};
|
|
256
269
|
credentials.refs[channel.apiKeyEnv] = channel.apiKey || '';
|
|
257
270
|
if (!isObject(credentials.records)) credentials.records = {};
|
|
258
|
-
|
|
271
|
+
writeYamlFile(this.nativePaths.credentials, credentials, {
|
|
272
|
+
atomic: true,
|
|
273
|
+
mode: 0o600,
|
|
274
|
+
customTags: DSH_YAML_SOURCE_TAGS
|
|
275
|
+
});
|
|
259
276
|
}
|
|
260
277
|
|
|
261
278
|
_providerEntry(channel, baseUrl = channel.baseUrl, models = channel.models) {
|
|
@@ -273,7 +290,10 @@ class DshChannelService extends BaseChannelService {
|
|
|
273
290
|
const settings = this._nativeSettings();
|
|
274
291
|
const llm = isObject(settings['llm-pi-ai']) ? { ...settings['llm-pi-ai'] } : {};
|
|
275
292
|
const providers = isObject(llm.providers) ? { ...llm.providers } : {};
|
|
276
|
-
providers[channel.providerKey] =
|
|
293
|
+
providers[channel.providerKey] = {
|
|
294
|
+
...(isObject(providers[channel.providerKey]) ? providers[channel.providerKey] : {}),
|
|
295
|
+
...this._providerEntry(channel)
|
|
296
|
+
};
|
|
277
297
|
settings['llm-pi-ai'] = { ...llm, providers };
|
|
278
298
|
settings['agent-default-model'] = {
|
|
279
299
|
...(isObject(settings['agent-default-model']) ? settings['agent-default-model'] : {}),
|
|
@@ -149,24 +149,6 @@ function createDriver(context = {}) {
|
|
|
149
149
|
{ authSourceType: 'dsh' }
|
|
150
150
|
);
|
|
151
151
|
}),
|
|
152
|
-
speedTestAll: request => withAsyncResult(request, 'speedTestAll', async () => {
|
|
153
|
-
const { testMultipleChannels } = require('../../../server/services/speed-test');
|
|
154
|
-
const body = bodyOf(request);
|
|
155
|
-
const channels = implementation.getServiceInstance().getEnabledChannels();
|
|
156
|
-
const groups = new Map();
|
|
157
|
-
for (const channel of channels) {
|
|
158
|
-
const type = channelTypeForSpeed(channel);
|
|
159
|
-
const group = groups.get(type) || [];
|
|
160
|
-
group.push(channel);
|
|
161
|
-
groups.set(type, group);
|
|
162
|
-
}
|
|
163
|
-
const groupedResults = await Promise.all([...groups.entries()].map(([type, group]) => (
|
|
164
|
-
testMultipleChannels(group, body.timeout, type, body.concurrency)
|
|
165
|
-
)));
|
|
166
|
-
const resultById = new Map(groupedResults.flat().map(result => [result.channelId, result]));
|
|
167
|
-
const results = channels.map(channel => resultById.get(channel.id)).filter(Boolean);
|
|
168
|
-
return { results };
|
|
169
|
-
}),
|
|
170
152
|
saveOrder: request => withResult(request, 'order', () => {
|
|
171
153
|
const body = bodyOf(request);
|
|
172
154
|
implementation.getServiceInstance().saveChannelOrder(body.order || body.ids || []);
|
|
@@ -23,6 +23,12 @@ const DSH_YAML_SCHEMA = yaml.DEFAULT_SCHEMA.extend([
|
|
|
23
23
|
})
|
|
24
24
|
]);
|
|
25
25
|
|
|
26
|
+
const DSH_YAML_SOURCE_TAGS = [{
|
|
27
|
+
tag: 'tag:yaml.org,2002:js',
|
|
28
|
+
resolve: value => `!!js ${value}`,
|
|
29
|
+
stringify: item => String(item.value).replace(/^!!js\s*/, '')
|
|
30
|
+
}];
|
|
31
|
+
|
|
26
32
|
function clone(value) {
|
|
27
33
|
if (value === undefined) return undefined;
|
|
28
34
|
return JSON.parse(JSON.stringify(value));
|
|
@@ -548,6 +554,7 @@ module.exports = {
|
|
|
548
554
|
resolvePaths,
|
|
549
555
|
isObject,
|
|
550
556
|
readYamlFile,
|
|
557
|
+
DSH_YAML_SOURCE_TAGS,
|
|
551
558
|
readJsonFile,
|
|
552
559
|
fileRevision,
|
|
553
560
|
writeAtomic,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
|
-
const {
|
|
4
|
+
const { writeYamlFile } = require('../../../utils/native-config-patcher');
|
|
5
|
+
const { resolvePaths, readYamlFile, mergeSettings, fileRevision, credentialMetadata, redactSecrets, listProfiles, listProfilePlugins, listProfileMcp, listProfilePrompts, DSH_YAML_SOURCE_TAGS } = require('./common');
|
|
5
6
|
const { resolveSkillRoots } = require('./resources');
|
|
6
7
|
|
|
7
8
|
function createDriver(context = {}) {
|
|
@@ -35,7 +36,11 @@ function createDriver(context = {}) {
|
|
|
35
36
|
}
|
|
36
37
|
const current = readYamlFile(paths.settings, {});
|
|
37
38
|
const next = mergeSettings(current, body.namespace, body.patch, body.replace === true);
|
|
38
|
-
|
|
39
|
+
writeYamlFile(paths.settings, next, {
|
|
40
|
+
atomic: true,
|
|
41
|
+
mode: 0o600,
|
|
42
|
+
customTags: DSH_YAML_SOURCE_TAGS
|
|
43
|
+
});
|
|
39
44
|
return this.getConfig();
|
|
40
45
|
},
|
|
41
46
|
getConfigCapabilities() {
|
|
@@ -3,8 +3,9 @@ const path = require('path');
|
|
|
3
3
|
const crypto = require('crypto');
|
|
4
4
|
const { PATHS, NATIVE_PATHS } = require('../../../config/paths');
|
|
5
5
|
const { resolveChannelWebsiteUrl } = require('../../../config/channel-preset-websites');
|
|
6
|
-
const {
|
|
6
|
+
const { readNativeOAuth, clearGeminiChannelConfig } = require('../../native-oauth-adapters');
|
|
7
7
|
const { normalizeGatewaySourceType } = require('../../../shared/proxy-utils');
|
|
8
|
+
const { readJsoncFile, updateJsoncFile, updateEnvFile } = require('../../../utils/native-config-patcher');
|
|
8
9
|
const {
|
|
9
10
|
createSkippedResult,
|
|
10
11
|
isLocalProxyBaseUrl,
|
|
@@ -84,12 +85,16 @@ function applyNativeGeminiOAuth(channel) {
|
|
|
84
85
|
clearGeminiChannelConfig();
|
|
85
86
|
|
|
86
87
|
const settingsPath = configuredNative.settings;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
88
|
+
updateJsoncFile(settingsPath, (settings) => {
|
|
89
|
+
settings.security = settings.security && typeof settings.security === 'object' && !Array.isArray(settings.security)
|
|
90
|
+
? settings.security
|
|
91
|
+
: {};
|
|
92
|
+
settings.security.auth = settings.security.auth && typeof settings.security.auth === 'object'
|
|
93
|
+
&& !Array.isArray(settings.security.auth)
|
|
94
|
+
? settings.security.auth
|
|
95
|
+
: {};
|
|
96
|
+
settings.security.auth.selectedType = 'oauth-personal';
|
|
97
|
+
});
|
|
93
98
|
console.log(`[Gemini Channels] Applied native OAuth channel ${channel.name}`);
|
|
94
99
|
return channel;
|
|
95
100
|
}
|
|
@@ -168,14 +173,11 @@ function readExistingGeminiEnv() {
|
|
|
168
173
|
|
|
169
174
|
function writeGeminiEnv(env = {}) {
|
|
170
175
|
const envPath = path.join(getGeminiDir(), '.env');
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (process.platform !== 'win32') {
|
|
177
|
-
fs.chmodSync(envPath, 0o600);
|
|
178
|
-
}
|
|
176
|
+
const managedKeys = ['GOOGLE_GEMINI_BASE_URL', 'GEMINI_API_KEY', 'GEMINI_MODEL'];
|
|
177
|
+
updateEnvFile(envPath, Object.fromEntries(managedKeys.map(key => [
|
|
178
|
+
key,
|
|
179
|
+
Object.prototype.hasOwnProperty.call(env, key) ? env[key] : undefined
|
|
180
|
+
])), { atomic: true, mode: 0o600 });
|
|
179
181
|
}
|
|
180
182
|
|
|
181
183
|
// 检查是否在代理模式
|
|
@@ -515,8 +517,6 @@ function applyChannelToSettings(channelId, channels = null) {
|
|
|
515
517
|
return applyNativeGeminiOAuth(channel);
|
|
516
518
|
}
|
|
517
519
|
|
|
518
|
-
clearNativeOAuth('gemini');
|
|
519
|
-
|
|
520
520
|
const geminiDir = getGeminiDir();
|
|
521
521
|
|
|
522
522
|
if (!fs.existsSync(geminiDir)) {
|
|
@@ -532,22 +532,16 @@ function applyChannelToSettings(channelId, channels = null) {
|
|
|
532
532
|
|
|
533
533
|
// 确保 settings.json 存在并配置正确的认证模式
|
|
534
534
|
const settingsPath = path.join(geminiDir, 'settings.json');
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
// 设置认证模式为 gemini-api-key(第三方 API)
|
|
546
|
-
settings.security = settings.security || {};
|
|
547
|
-
settings.security.auth = settings.security.auth || {};
|
|
548
|
-
settings.security.auth.selectedType = 'gemini-api-key';
|
|
549
|
-
|
|
550
|
-
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
535
|
+
updateJsoncFile(settingsPath, (settings) => {
|
|
536
|
+
settings.security = settings.security && typeof settings.security === 'object' && !Array.isArray(settings.security)
|
|
537
|
+
? settings.security
|
|
538
|
+
: {};
|
|
539
|
+
settings.security.auth = settings.security.auth && typeof settings.security.auth === 'object'
|
|
540
|
+
&& !Array.isArray(settings.security.auth)
|
|
541
|
+
? settings.security.auth
|
|
542
|
+
: {};
|
|
543
|
+
settings.security.auth.selectedType = 'gemini-api-key';
|
|
544
|
+
});
|
|
551
545
|
|
|
552
546
|
console.log(`[Gemini Channels] Applied channel ${channel.name} to .env`);
|
|
553
547
|
return channel;
|
|
@@ -600,6 +594,17 @@ function writeGeminiConfigForMultiChannel(allChannels) {
|
|
|
600
594
|
delete env.GEMINI_API_KEY;
|
|
601
595
|
delete env.GEMINI_MODEL;
|
|
602
596
|
writeGeminiEnv(env);
|
|
597
|
+
const settingsPath = path.join(geminiDir, 'settings.json');
|
|
598
|
+
if (fs.existsSync(settingsPath)) {
|
|
599
|
+
updateJsoncFile(settingsPath, (settings) => {
|
|
600
|
+
const auth = settings.security?.auth;
|
|
601
|
+
if (auth?.selectedType === 'gemini-api-key') {
|
|
602
|
+
delete auth.selectedType;
|
|
603
|
+
if (Object.keys(auth).length === 0) delete settings.security.auth;
|
|
604
|
+
if (Object.keys(settings.security).length === 0) delete settings.security;
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
}
|
|
603
608
|
return;
|
|
604
609
|
}
|
|
605
610
|
|
|
@@ -611,22 +616,13 @@ function writeGeminiConfigForMultiChannel(allChannels) {
|
|
|
611
616
|
|
|
612
617
|
// 确保 settings.json 存在并配置正确的认证模式
|
|
613
618
|
const settingsPath = path.join(geminiDir, 'settings.json');
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
// 设置认证模式为 gemini-api-key(第三方 API)
|
|
625
|
-
settings.security = settings.security || {};
|
|
626
|
-
settings.security.auth = settings.security.auth || {};
|
|
627
|
-
settings.security.auth.selectedType = 'gemini-api-key';
|
|
628
|
-
|
|
629
|
-
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
619
|
+
updateJsoncFile(settingsPath, (settings) => {
|
|
620
|
+
settings.security = settings.security && typeof settings.security === 'object' ? settings.security : {};
|
|
621
|
+
settings.security.auth = settings.security.auth && typeof settings.security.auth === 'object'
|
|
622
|
+
? settings.security.auth
|
|
623
|
+
: {};
|
|
624
|
+
settings.security.auth.selectedType = 'gemini-api-key';
|
|
625
|
+
});
|
|
630
626
|
}
|
|
631
627
|
|
|
632
628
|
// 获取所有启用的渠道(供调度器使用)
|
|
@@ -682,7 +678,7 @@ function disableAllChannels() {
|
|
|
682
678
|
data.channels.forEach(ch => { ch.enabled = false; });
|
|
683
679
|
saveChannels(data);
|
|
684
680
|
if (!getGeminiProxyState().running) {
|
|
685
|
-
|
|
681
|
+
writeGeminiConfigForMultiChannel(data.channels);
|
|
686
682
|
}
|
|
687
683
|
}
|
|
688
684
|
|
|
@@ -691,11 +687,7 @@ function readGeminiSettings() {
|
|
|
691
687
|
if (!settingsPath || !fs.existsSync(settingsPath)) {
|
|
692
688
|
return {};
|
|
693
689
|
}
|
|
694
|
-
|
|
695
|
-
return JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
696
|
-
} catch {
|
|
697
|
-
return {};
|
|
698
|
-
}
|
|
690
|
+
return readJsoncFile(settingsPath);
|
|
699
691
|
}
|
|
700
692
|
|
|
701
693
|
function normalizeGeminiChannel(channel = {}) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { NATIVE_PATHS } = require('../../../config/paths');
|
|
4
|
+
const { readJsoncFile, writeJsoncFile, updateEnvFile } = require('../../../utils/native-config-patcher');
|
|
4
5
|
const DEFAULT_NATIVE_PATHS = NATIVE_PATHS.gemini;
|
|
5
6
|
let nativePaths = DEFAULT_NATIVE_PATHS;
|
|
6
7
|
|
|
@@ -64,25 +65,14 @@ function readEnv() {
|
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
// 将环境对象转换为 .env 字符串
|
|
68
|
-
function envToString(env) {
|
|
69
|
-
let content = '';
|
|
70
|
-
for (const [key, value] of Object.entries(env)) {
|
|
71
|
-
content += `${key}=${value}\n`;
|
|
72
|
-
}
|
|
73
|
-
return content;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
68
|
// 写入 .env
|
|
77
69
|
function writeEnv(env) {
|
|
78
70
|
try {
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
fs.chmodSync(getEnvPath(), 0o600);
|
|
85
|
-
}
|
|
71
|
+
const managedKeys = ['GOOGLE_GEMINI_BASE_URL', 'GEMINI_API_KEY', 'GEMINI_MODEL'];
|
|
72
|
+
updateEnvFile(getEnvPath(), Object.fromEntries(managedKeys.map(key => [
|
|
73
|
+
key,
|
|
74
|
+
Object.prototype.hasOwnProperty.call(env || {}, key) ? env[key] : undefined
|
|
75
|
+
])), { atomic: true, mode: 0o600 });
|
|
86
76
|
} catch (err) {
|
|
87
77
|
throw new Error('Failed to write .env: ' + err.message);
|
|
88
78
|
}
|
|
@@ -94,8 +84,7 @@ function readSettings() {
|
|
|
94
84
|
if (!settingsExists()) {
|
|
95
85
|
return {};
|
|
96
86
|
}
|
|
97
|
-
|
|
98
|
-
return JSON.parse(content);
|
|
87
|
+
return readJsoncFile(getSettingsPath());
|
|
99
88
|
} catch (err) {
|
|
100
89
|
throw new Error('Failed to read settings.json: ' + err.message);
|
|
101
90
|
}
|
|
@@ -104,8 +93,7 @@ function readSettings() {
|
|
|
104
93
|
// 写入 settings.json
|
|
105
94
|
function writeSettings(settings) {
|
|
106
95
|
try {
|
|
107
|
-
|
|
108
|
-
fs.writeFileSync(getSettingsPath(), content, 'utf8');
|
|
96
|
+
writeJsoncFile(getSettingsPath(), settings);
|
|
109
97
|
} catch (err) {
|
|
110
98
|
throw new Error('Failed to write settings.json: ' + err.message);
|
|
111
99
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { createNativeSnapshotMethods } = require('./native-config-snapshot');
|
|
4
|
+
|
|
5
|
+
function createDriver(context = {}) {
|
|
6
|
+
return {
|
|
7
|
+
platform: context.platform,
|
|
8
|
+
capability: 'nativeConfig',
|
|
9
|
+
...context,
|
|
10
|
+
...createNativeSnapshotMethods({}, {
|
|
11
|
+
platform: context.platform,
|
|
12
|
+
runtime: context.runtime,
|
|
13
|
+
manifest: context.manifest,
|
|
14
|
+
pathContext: context.pathContext,
|
|
15
|
+
paths: context.paths
|
|
16
|
+
})
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = { createDriver };
|