coding-tool-x 3.4.0 → 3.4.1

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.
Files changed (27) hide show
  1. package/dist/web/assets/{Analytics-DEjfL5Jx.js → Analytics-CbGxotgz.js} +1 -1
  2. package/dist/web/assets/{ConfigTemplates-DkRL_-tf.js → ConfigTemplates-oP6nrFEb.js} +1 -1
  3. package/dist/web/assets/{Home-CF-L640I.js → Home-DMntmEvh.js} +1 -1
  4. package/dist/web/assets/{PluginManager-BzNYTdNB.js → PluginManager-BUC_c7nH.js} +1 -1
  5. package/dist/web/assets/{ProjectList-C0-JgHMM.js → ProjectList-CW8J49n7.js} +1 -1
  6. package/dist/web/assets/{SessionList-CkZUdX5N.js → SessionList-7lYnF92v.js} +1 -1
  7. package/dist/web/assets/{SkillManager-Cak0-4d4.js → SkillManager-Cs08216i.js} +1 -1
  8. package/dist/web/assets/{WorkspaceManager-CGDJzwEr.js → WorkspaceManager-CY-oGtyB.js} +1 -1
  9. package/dist/web/assets/{index-Dz7v9OM0.css → index-5qy5NMIP.css} +1 -1
  10. package/dist/web/assets/index-ClCqKpvX.js +2 -0
  11. package/dist/web/index.html +2 -2
  12. package/package.json +6 -2
  13. package/src/server/codex-proxy-server.js +4 -92
  14. package/src/server/gemini-proxy-server.js +5 -28
  15. package/src/server/opencode-proxy-server.js +3 -93
  16. package/src/server/proxy-server.js +2 -57
  17. package/src/server/services/base/base-channel-service.js +247 -0
  18. package/src/server/services/base/proxy-utils.js +152 -0
  19. package/src/server/services/channel-health.js +30 -19
  20. package/src/server/services/channels.js +125 -293
  21. package/src/server/services/codex-channels.js +148 -513
  22. package/src/server/services/codex-env-manager.js +49 -19
  23. package/src/server/services/gemini-channels.js +2 -7
  24. package/src/server/services/oauth-credentials-service.js +12 -2
  25. package/src/server/services/opencode-channels.js +7 -9
  26. package/src/server/services/repo-scanner-base.js +1 -0
  27. package/dist/web/assets/index-D_WItvHE.js +0 -2
@@ -77,6 +77,22 @@ function upsertManagedBlock(content, snippet) {
77
77
  return `${stripped.trimEnd()}\n\n${snippet}\n`;
78
78
  }
79
79
 
80
+ function buildExportCommand(envFilePath, homeDir) {
81
+ return `. "${buildHomeRelativeShellPath(envFilePath, homeDir)}"`;
82
+ }
83
+
84
+ function buildPosixEnvFileContent(nextValues) {
85
+ const keys = Object.keys(nextValues).sort();
86
+ if (!keys.length) {
87
+ return '';
88
+ }
89
+ return [
90
+ '# Managed by Coding-Tool',
91
+ ...keys.map((key) => `export ${key}=${shellQuote(nextValues[key])}`),
92
+ ''
93
+ ].join('\n');
94
+ }
95
+
80
96
  function readJsonFile(filePath, fallbackValue) {
81
97
  try {
82
98
  if (!fs.existsSync(filePath)) {
@@ -181,29 +197,43 @@ function syncPosixEnvironment(nextValues, previousState, options) {
181
197
  const {
182
198
  runtime,
183
199
  homeDir,
200
+ envFilePath,
184
201
  stateFilePath,
185
202
  shellEnv,
186
203
  execSync
187
204
  } = options;
188
205
  const nextKeys = Object.keys(nextValues).sort();
189
206
  let changed = false;
207
+ const { preferred, candidates } = getPosixProfileCandidates(homeDir, shellEnv);
208
+ const sourceSnippet = buildSourceSnippet(envFilePath, homeDir);
209
+ const sourceCommand = buildExportCommand(envFilePath, homeDir);
190
210
 
191
211
  // 清理旧版本遗留的 shell profile 注入(迁移兼容)
192
212
  const previousProfiles = Array.isArray(previousState.profiles) ? previousState.profiles : [];
193
- if (previousProfiles.length > 0) {
194
- const { candidates } = getPosixProfileCandidates(homeDir, shellEnv);
195
- const cleanupTargets = new Set([
196
- ...previousProfiles,
197
- ...candidates.filter(filePath => fs.existsSync(filePath))
198
- ]);
199
- for (const profilePath of cleanupTargets) {
200
- if (!fs.existsSync(profilePath)) continue;
201
- const currentContent = fs.readFileSync(profilePath, 'utf8');
202
- if (!currentContent.includes(PROFILE_MARKER_START)) continue;
203
- const nextContent = stripManagedBlock(currentContent);
204
- const finalContent = nextContent ? `${nextContent}\n` : '';
205
- changed = writeTextFileIfChanged(profilePath, finalContent) || changed;
206
- }
213
+ const cleanupTargets = new Set([
214
+ ...previousProfiles,
215
+ ...candidates.filter((filePath) => fs.existsSync(filePath))
216
+ ]);
217
+ if (!nextKeys.length) {
218
+ cleanupTargets.add(preferred);
219
+ }
220
+ for (const profilePath of cleanupTargets) {
221
+ if (!fs.existsSync(profilePath)) continue;
222
+ const currentContent = fs.readFileSync(profilePath, 'utf8');
223
+ if (!currentContent.includes(PROFILE_MARKER_START)) continue;
224
+ const nextContent = stripManagedBlock(currentContent);
225
+ const finalContent = nextContent ? `${nextContent}\n` : '';
226
+ changed = writeTextFileIfChanged(profilePath, finalContent) || changed;
227
+ }
228
+
229
+ if (nextKeys.length > 0) {
230
+ changed = writeTextFileIfChanged(envFilePath, buildPosixEnvFileContent(nextValues)) || changed;
231
+ const currentProfileContent = fs.existsSync(preferred) ? fs.readFileSync(preferred, 'utf8') : '';
232
+ const nextProfileContent = upsertManagedBlock(currentProfileContent, sourceSnippet);
233
+ changed = writeTextFileIfChanged(preferred, nextProfileContent) || changed;
234
+ } else if (fs.existsSync(envFilePath)) {
235
+ fs.unlinkSync(envFilePath);
236
+ changed = true;
207
237
  }
208
238
 
209
239
  // macOS:用 launchctl 写入全局环境变量,新开终端/进程即生效
@@ -225,7 +255,7 @@ function syncPosixEnvironment(nextValues, previousState, options) {
225
255
  writeJsonFile(stateFilePath, {
226
256
  version: 1,
227
257
  values: nextValues,
228
- profiles: []
258
+ profiles: [preferred]
229
259
  });
230
260
  } else if (fs.existsSync(stateFilePath)) {
231
261
  fs.unlinkSync(stateFilePath);
@@ -235,10 +265,10 @@ function syncPosixEnvironment(nextValues, previousState, options) {
235
265
  changed,
236
266
  reloadRequired: changed,
237
267
  isFirstTime: Object.keys(previousState.values || {}).length === 0 && nextKeys.length > 0,
238
- sourceCommand: null,
239
- shellConfigPath: null,
240
- shellConfigPaths: [],
241
- envFilePath: null,
268
+ sourceCommand: nextKeys.length > 0 ? sourceCommand : null,
269
+ shellConfigPath: nextKeys.length > 0 ? preferred : null,
270
+ shellConfigPaths: nextKeys.length > 0 ? [preferred] : [],
271
+ envFilePath: nextKeys.length > 0 ? envFilePath : null,
242
272
  managedKeys: nextKeys
243
273
  };
244
274
  }
@@ -3,6 +3,7 @@ const path = require('path');
3
3
  const crypto = require('crypto');
4
4
  const { PATHS, NATIVE_PATHS } = require('../../config/paths');
5
5
  const { clearNativeOAuth } = require('./native-oauth-adapters');
6
+ const { normalizeGatewaySourceType } = require('./base/proxy-utils');
6
7
 
7
8
  /**
8
9
  * Gemini 渠道管理服务(多渠道架构)
@@ -17,13 +18,7 @@ const { clearNativeOAuth } = require('./native-oauth-adapters');
17
18
  * - 使用 weight 和 maxConcurrency 控制负载均衡
18
19
  */
19
20
 
20
- function normalizeGatewaySourceType(value, fallback = 'gemini') {
21
- const normalized = String(value || '').trim().toLowerCase();
22
- if (normalized === 'claude') return 'claude';
23
- if (normalized === 'codex') return 'codex';
24
- if (normalized === 'gemini') return 'gemini';
25
- return fallback;
26
- }
21
+ // normalizeGatewaySourceType imported from base/proxy-utils
27
22
 
28
23
  // 获取 Gemini 配置目录
29
24
  function getGeminiDir() {
@@ -380,12 +380,21 @@ function stableFingerprintValue(tool, metadata) {
380
380
  return stableId;
381
381
  }
382
382
 
383
+ function resolveFingerprintValue(tool, metadata, options = {}) {
384
+ if (options.fingerprintMode === 'primary-token') {
385
+ return metadata.primaryToken
386
+ || metadata.accessToken
387
+ || stableFingerprintValue(tool, metadata);
388
+ }
389
+ return stableFingerprintValue(tool, metadata);
390
+ }
391
+
383
392
  function upsertCredential(tool, metadata, options = {}) {
384
393
  const store = readStore();
385
394
  const toolStore = getToolStore(store, tool);
386
395
  const now = Date.now();
387
396
  const primaryToken = metadata.primaryToken || metadata.accessToken || '';
388
- const fingerprint = fingerprintFor(tool, stableFingerprintValue(tool, metadata));
397
+ const fingerprint = fingerprintFor(tool, resolveFingerprintValue(tool, metadata, options));
389
398
  const existingIndex = toolStore.credentials.findIndex((item) => item.fingerprint === fingerprint);
390
399
  const existing = existingIndex >= 0 ? toolStore.credentials[existingIndex] : null;
391
400
 
@@ -456,7 +465,8 @@ function syncLocalCredential(tool) {
456
465
  }
457
466
 
458
467
  const credentials = nativeCredentials.map((metadata) => upsertCredential(tool, metadata, {
459
- source: 'synced-local'
468
+ source: 'synced-local',
469
+ fingerprintMode: 'primary-token'
460
470
  }));
461
471
 
462
472
  return {
@@ -4,18 +4,15 @@ const crypto = require('crypto');
4
4
  const { PATHS } = require('../../config/paths');
5
5
  const { clearNativeOAuth } = require('./native-oauth-adapters');
6
6
  const { setChannelConfig } = require('./opencode-settings-manager');
7
+ const { normalizeGatewaySourceType } = require('./base/proxy-utils');
7
8
 
8
9
  /**
9
10
  * OpenCode 渠道管理服务
10
11
  * 存储位置: ~/.cc-tool/opencode-channels.json
11
12
  */
12
13
 
13
- function normalizeGatewaySourceType(value) {
14
- const normalized = String(value || '').trim().toLowerCase();
15
- if (normalized === 'claude') return 'claude';
16
- if (normalized === 'gemini') return 'gemini';
17
- return 'codex';
18
- }
14
+ // normalizeGatewaySourceType imported from base/proxy-utils
15
+ // OpenCode default fallback is 'codex'
19
16
 
20
17
  function normalizeApiKey(value) {
21
18
  if (typeof value !== 'string') return '';
@@ -80,7 +77,7 @@ function loadChannels() {
80
77
  modelRedirects: ch.modelRedirects || [],
81
78
  speedTestModel: ch.speedTestModel || null,
82
79
  wireApi: ch.wireApi || 'openai', // OpenCode 默认使用 OpenAI 兼容格式
83
- gatewaySourceType: normalizeGatewaySourceType(ch.gatewaySourceType),
80
+ gatewaySourceType: normalizeGatewaySourceType(ch.gatewaySourceType, 'codex'),
84
81
  allowedModels: ch.allowedModels || []
85
82
  };
86
83
  normalized.providerKey = deriveProviderKey(normalized);
@@ -132,7 +129,7 @@ function createChannel(name, baseUrl, apiKey, extraConfig = {}) {
132
129
  modelRedirects: extraConfig.modelRedirects || [],
133
130
  speedTestModel: extraConfig.speedTestModel || null,
134
131
  model: extraConfig.model || null,
135
- gatewaySourceType: normalizeGatewaySourceType(extraConfig.gatewaySourceType),
132
+ gatewaySourceType: normalizeGatewaySourceType(extraConfig.gatewaySourceType, 'codex'),
136
133
  providerKey: extraConfig.providerKey || null,
137
134
  presetId: extraConfig.presetId || null,
138
135
  websiteUrl: extraConfig.websiteUrl || '',
@@ -169,7 +166,8 @@ function updateChannel(channelId, updates) {
169
166
  gatewaySourceType: normalizeGatewaySourceType(
170
167
  updates.gatewaySourceType !== undefined
171
168
  ? updates.gatewaySourceType
172
- : oldChannel.gatewaySourceType
169
+ : oldChannel.gatewaySourceType,
170
+ 'codex'
173
171
  ),
174
172
  updatedAt: Date.now()
175
173
  };
@@ -7,6 +7,7 @@
7
7
 
8
8
  const fs = require('fs');
9
9
  const path = require('path');
10
+ const os = require('os');
10
11
  const https = require('https');
11
12
  const http = require('http');
12
13
  const { createWriteStream } = require('fs');