base_parts_ai 1.0.53 → 1.0.55

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/lib/ai_utils.js CHANGED
@@ -117,7 +117,7 @@ async function fetchConfig(buildCfg) {
117
117
  result.data.channels.forEach(function (ch) {
118
118
  API_CHANNELS.push(ch);
119
119
  });
120
- console.log('[fetchConfig] 已拉取 ' + API_CHANNELS.length + ' 个渠道');
120
+ // console.log('[fetchConfig] 已拉取 ' + API_CHANNELS.length + ' 个渠道');
121
121
  }
122
122
  // 将服务端返回的 Codex 渠道列表填充到 CX_CHANNELS
123
123
  if (Array.isArray(result.data.cxChannels)) {
@@ -125,7 +125,7 @@ async function fetchConfig(buildCfg) {
125
125
  result.data.cxChannels.forEach(function (ch) {
126
126
  CX_CHANNELS.push(ch);
127
127
  });
128
- console.log('[fetchConfig] 已拉取 ' + CX_CHANNELS.length + ' 个 Codex 渠道');
128
+ // console.log('[fetchConfig] 已拉取 ' + CX_CHANNELS.length + ' 个 Codex 渠道');
129
129
  }
130
130
  // 将服务端返回的 weds 渠道列表填充到 WEDS_CHANNELS
131
131
  if (Array.isArray(result.data.wedsChannels)) {
@@ -133,7 +133,7 @@ async function fetchConfig(buildCfg) {
133
133
  result.data.wedsChannels.forEach(function (ch) {
134
134
  WEDS_CHANNELS.push(ch);
135
135
  });
136
- console.log('[fetchConfig] 已拉取 ' + WEDS_CHANNELS.length + ' 个 weds 渠道');
136
+ // console.log('[fetchConfig] 已拉取 ' + WEDS_CHANNELS.length + ' 个 weds 渠道');
137
137
  }
138
138
  // 解析公告列表
139
139
  if (Array.isArray(result.data.noticeList)) {
package/lib/setapi_cc.js CHANGED
@@ -9,9 +9,36 @@
9
9
 
10
10
  // inquirer@8 使用传统 prompt API
11
11
  var inquirer = require('inquirer');
12
+ var fs = require('fs');
12
13
  var utils = require('./ai_utils');
13
14
  var childProcess = require('child_process');
14
15
 
16
+ // Claude 配置清除选项使用固定内部值,避免与服务端渠道 id 冲突。
17
+ var CLEAR_CLAUDE_CONFIG = '__clear_claude_config__';
18
+
19
+ /**
20
+ * 校验手动输入的 URL 或 Key,禁止再通过空白输入触发删除操作
21
+ * @param {string} value 用户输入值
22
+ * @returns {boolean|string}
23
+ */
24
+ function validateRequiredInput(value) {
25
+ return String(value || '').trim().length > 0 ? true : '配置不能为空,请输入有效内容';
26
+ }
27
+
28
+ /**
29
+ * 删除 Claude 的两份生效配置文件,恢复客户端官方默认配置
30
+ * @param {object} buildCfg Claude 配置路径
31
+ */
32
+ function clearClaudeConfig(buildCfg) {
33
+ [buildCfg.claudeJsonPath, buildCfg.claudeSettingsPath].forEach(function (filePath) {
34
+ try {
35
+ fs.rmSync(filePath, { force: true });
36
+ } catch (e) {
37
+ throw new Error('清除 Claude 配置失败(' + filePath + '): ' + e.message);
38
+ }
39
+ });
40
+ }
41
+
15
42
  /**
16
43
  * 校验并安装渠道要求的 Claude Code 版本
17
44
  * @param {string} requiredVer 渠道要求版本,latest 表示仅确保已安装
@@ -50,10 +77,6 @@ async function setapiCc(cmd, buildCfg) {
50
77
  // 显示当前配置状态
51
78
  utils.printCurrentInfo(settings, channels);
52
79
 
53
- if (!channels.length) {
54
- throw new Error('Claude 渠道列表为空,请检查网络或服务端配置');
55
- }
56
-
57
80
  // 构建渠道选择列表
58
81
  var choices = channels.map(function (c) {
59
82
  return {
@@ -61,6 +84,11 @@ async function setapiCc(cmd, buildCfg) {
61
84
  value: c.id,
62
85
  };
63
86
  });
87
+ // 清除入口固定放在渠道列表最后,即使服务端渠道为空也可恢复官方配置。
88
+ choices.push({
89
+ name: '>>>>清除Claude配置<<<< — 删除并恢复官方默认配置',
90
+ value: CLEAR_CLAUDE_CONFIG,
91
+ });
64
92
 
65
93
  // 弹出渠道选择列表,默认选中当前渠道
66
94
  var answers = await inquirer.prompt([{
@@ -72,6 +100,26 @@ async function setapiCc(cmd, buildCfg) {
72
100
  }]);
73
101
  var channelId = answers.channelId;
74
102
 
103
+ if (channelId === CLEAR_CLAUDE_CONFIG) {
104
+ var clearAnswer = await inquirer.prompt([{
105
+ type: 'confirm',
106
+ name: 'confirmed',
107
+ message: '确认删除 Claude 配置文件并恢复官方默认配置吗?',
108
+ default: false,
109
+ }]);
110
+ if (!clearAnswer.confirmed) {
111
+ console.log('已取消清除 Claude 配置。');
112
+ return;
113
+ }
114
+ clearClaudeConfig(buildCfg);
115
+ console.log('✅ 已清除 Claude 配置,客户端将使用官方默认配置。');
116
+ return;
117
+ }
118
+
119
+ if (!channels.length) {
120
+ throw new Error('Claude 渠道列表为空,请检查网络或服务端配置');
121
+ }
122
+
75
123
  // 找到选中的渠道配置
76
124
  var selected = channels.find(function (c) { return c.id === channelId; });
77
125
 
@@ -121,20 +169,16 @@ async function setapiCc(cmd, buildCfg) {
121
169
  var baseUrlAnswer = await inquirer.prompt([{
122
170
  type: 'input',
123
171
  name: 'value',
124
- message: '请输入 ANTHROPIC_BASE_URL(输入空格后回车删除该配置):',
172
+ message: '请输入 ANTHROPIC_BASE_URL:',
125
173
  default: defaultBaseUrl,
174
+ validate: validateRequiredInput,
126
175
  }]);
127
176
  var inputBaseUrl = baseUrlAnswer.value.trim();
128
177
 
129
- // 缓存或删除 BASE_URL,空值表示清理该环境变量配置
178
+ // 手动 URL 通过非空校验后写入配置及渠道缓存。
130
179
  if (!jccJson.keys) { jccJson.keys = {}; }
131
- if (inputBaseUrl) {
132
- settings.env.ANTHROPIC_BASE_URL = inputBaseUrl;
133
- jccJson.keys[selected.id + '_baseUrl'] = inputBaseUrl;
134
- } else {
135
- delete settings.env.ANTHROPIC_BASE_URL;
136
- delete jccJson.keys[selected.id + '_baseUrl'];
137
- }
180
+ settings.env.ANTHROPIC_BASE_URL = inputBaseUrl;
181
+ jccJson.keys[selected.id + '_baseUrl'] = inputBaseUrl;
138
182
 
139
183
  if (forcedAuthToken) {
140
184
  // 服务端强制 Token 不写入手动 Token 缓存,避免污染后续默认值
@@ -146,18 +190,14 @@ async function setapiCc(cmd, buildCfg) {
146
190
  var tokenAnswer = await inquirer.prompt([{
147
191
  type: 'input',
148
192
  name: 'value',
149
- message: '请输入 ANTHROPIC_AUTH_TOKEN(输入空格后回车删除该配置):',
193
+ message: '请输入 ANTHROPIC_AUTH_TOKEN:',
150
194
  default: defaultToken,
195
+ validate: validateRequiredInput,
151
196
  }]);
152
197
  var inputToken = tokenAnswer.value.trim();
153
- if (inputToken) {
154
- settings.env.ANTHROPIC_AUTH_TOKEN = inputToken;
155
- jccJson.keys[selected.id] = inputToken;
156
- utils.saveLastManualAuthToken(jccJson, inputToken);
157
- } else {
158
- delete settings.env.ANTHROPIC_AUTH_TOKEN;
159
- delete jccJson.keys[selected.id];
160
- }
198
+ settings.env.ANTHROPIC_AUTH_TOKEN = inputToken;
199
+ jccJson.keys[selected.id] = inputToken;
200
+ utils.saveLastManualAuthToken(jccJson, inputToken);
161
201
  }
162
202
  utils.writeJsonFile(cachePath, jccJson);
163
203
 
@@ -178,9 +218,7 @@ async function setapiCc(cmd, buildCfg) {
178
218
  name: 'value',
179
219
  message: '请输入 ANTHROPIC_AUTH_TOKEN:',
180
220
  default: savedKey,
181
- validate: function (v) {
182
- return v.trim().length > 0 ? true : 'Key 不能为空';
183
- },
221
+ validate: validateRequiredInput,
184
222
  }]);
185
223
  var newKey = keyAnswer.value.trim();
186
224
  settings.env.ANTHROPIC_AUTH_TOKEN = newKey;
@@ -235,6 +273,9 @@ async function setapiCc(cmd, buildCfg) {
235
273
 
236
274
  setapiCc._private = {
237
275
  ensureClaudeVersion: ensureClaudeVersion,
276
+ validateRequiredInput: validateRequiredInput,
277
+ clearClaudeConfig: clearClaudeConfig,
278
+ CLEAR_CLAUDE_CONFIG: CLEAR_CLAUDE_CONFIG,
238
279
  };
239
280
 
240
281
  module.exports = setapiCc;
package/lib/setapi_cx.js CHANGED
@@ -10,7 +10,33 @@ var fs = require('fs');
10
10
  var inquirer = require('inquirer');
11
11
  var utils = require('./ai_utils');
12
12
  var childProcess = require('child_process');
13
- var TOML = require('smol-toml');
13
+ var TOML = require('@ltd/j-toml');
14
+
15
+ // Codex 配置清除选项使用固定内部值,避免与服务端渠道 id 冲突。
16
+ var CLEAR_CODEX_CONFIG = '__clear_codex_config__';
17
+
18
+ /**
19
+ * 校验手动输入的 URL 或 Key,禁止再通过空白输入触发删除操作
20
+ * @param {string} value 用户输入值
21
+ * @returns {boolean|string}
22
+ */
23
+ function validateRequiredInput(value) {
24
+ return String(value || '').trim().length > 0 ? true : '配置不能为空,请输入有效内容';
25
+ }
26
+
27
+ /**
28
+ * 删除 Codex 的两份生效配置文件,恢复客户端官方默认配置
29
+ * @param {object} buildCfg Codex 配置路径
30
+ */
31
+ function clearCodexConfig(buildCfg) {
32
+ [buildCfg.codexAuthPath, buildCfg.codexConfigPath].forEach(function (filePath) {
33
+ try {
34
+ fs.rmSync(filePath, { force: true });
35
+ } catch (e) {
36
+ throw new Error('清除 Codex 配置失败(' + filePath + '): ' + e.message);
37
+ }
38
+ });
39
+ }
14
40
 
15
41
  /**
16
42
  * 读取文本文件,文件不存在时返回空字符串
@@ -70,7 +96,12 @@ function parseTomlConfig(toml) {
70
96
  * @returns {string}
71
97
  */
72
98
  function stringifyTomlConfig(config) {
73
- var content = TOML.stringify(config || {});
99
+ var content = TOML.stringify(config || {}, {
100
+ newline: '\n',
101
+ newlineAround: 'section',
102
+ });
103
+ // j-toml 默认在文档开头保留空行,此处统一为原有配置文件格式。
104
+ content = content.replace(/^\n/, '');
74
105
  return content ? content.replace(/\s+$/, '') + '\n' : '';
75
106
  }
76
107
 
@@ -146,7 +177,7 @@ function hasTopLevelKey(toml, key) {
146
177
  function ensureSandboxMode(toml) {
147
178
  var config = parseTomlConfig(toml);
148
179
  if (!Object.prototype.hasOwnProperty.call(config, 'sandbox_mode')) {
149
- config.sandbox_mode = 'danger-full-access';
180
+ config.sandbox_mode = TOML.basic('danger-full-access');
150
181
  }
151
182
  return stringifyTomlConfig(config);
152
183
  }
@@ -210,55 +241,35 @@ function removeJcodexProviderBlock(toml) {
210
241
  * @param {string} toml 原 TOML 内容
211
242
  * @param {string} baseUrl provider base_url
212
243
  * @param {string} wireApi provider wire_api,空值表示删除该字段
244
+ * @param {string} openAiKey 当前渠道生效的 OPENAI_API_KEY
213
245
  * @returns {string}
214
246
  */
215
- function setJcodexProvider(toml, baseUrl, wireApi) {
247
+ function setJcodexProvider(toml, baseUrl, wireApi, openAiKey) {
248
+ if (typeof openAiKey !== 'string' || !openAiKey.trim()) {
249
+ throw new Error('写入 jcodex provider 时 OPENAI_API_KEY 不能为空');
250
+ }
216
251
  var config = parseTomlConfig(toml);
217
252
  var providers = ensureTable(config, 'model_providers');
218
- config.model_provider = 'jcodex';
219
- providers.jcodex = {
220
- name: 'jcodex',
221
- base_url: baseUrl,
222
- };
253
+ config.model_provider = TOML.basic('jcodex');
254
+ providers.jcodex = TOML.Section({
255
+ name: TOML.basic('jcodex'),
256
+ base_url: TOML.basic(baseUrl),
257
+
258
+ // jcodex 使用本地图片扩展身份 - 该方法 codex 0.149.0 版本会导致401错误,需要增加Authorization请求头
259
+ requires_openai_auth: false,
260
+ http_headers: TOML.inline({
261
+ 'x-openai-actor-authorization': TOML.basic('local-image-extension'),
262
+ Authorization: TOML.basic('Bearer ' + openAiKey),
263
+ }),
264
+
265
+ });
223
266
  // wire_api 是 Codex provider 私有字段,非空时强制写入到 model_providers.jcodex 下
224
267
  if (wireApi) {
225
- providers.jcodex.wire_api = wireApi;
226
- }
227
- return stringifyTomlConfig(config);
228
- }
229
-
230
- /**
231
- * 删除已有 jcodex provider 段中的 wire_api 字段,保留其它配置
232
- * @param {string} toml TOML 内容
233
- * @returns {string}
234
- */
235
- function removeJcodexWireApi(toml) {
236
- var config = parseTomlConfig(toml);
237
- var provider = config.model_providers && config.model_providers.jcodex;
238
- // wire_api 为空时只删除 jcodex provider 下的私有字段,不影响其它段落
239
- if (provider && typeof provider === 'object' && !Array.isArray(provider)) {
240
- delete provider.wire_api;
268
+ providers.jcodex.wire_api = TOML.basic(wireApi);
241
269
  }
242
270
  return stringifyTomlConfig(config);
243
271
  }
244
272
 
245
- /**
246
- * 写入 jcodex provider 段中的 wire_api 字段,已有字段会被强制覆盖
247
- * @param {string} toml TOML 内容
248
- * @param {string} wireApi provider wire_api
249
- * @returns {string}
250
- */
251
- function setJcodexWireApi(toml, wireApi) {
252
- var config = parseTomlConfig(toml);
253
- var providers = ensureTable(config, 'model_providers');
254
- var provider = ensureTable(providers, 'jcodex');
255
- if (!provider.name) {
256
- provider.name = 'jcodex';
257
- }
258
- provider.wire_api = wireApi;
259
- return stringifyTomlConfig(config);
260
- }
261
-
262
273
  /**
263
274
  * 从渠道 cxVersionList 中解析目标 Codex 版本
264
275
  * @param {object} selected 选中的 Codex 渠道配置
@@ -427,16 +438,17 @@ async function setapiCx(cmd, buildCfg) {
427
438
 
428
439
  printCurrentCodexInfo(auth, toml);
429
440
 
430
- if (!utils.CX_CHANNELS.length) {
431
- throw new Error('Codex 渠道列表为空,请检查网络或服务端 cxChannels 配置');
432
- }
433
-
434
441
  var choices = utils.CX_CHANNELS.map(function (c) {
435
442
  return {
436
443
  name: c.name + ' — ' + (c.description || ''),
437
444
  value: c.id,
438
445
  };
439
446
  });
447
+ // 清除入口固定放在渠道列表最后,即使服务端渠道为空也可恢复官方配置。
448
+ choices.push({
449
+ name: '>>>>清除Codex配置<<<< — 删除并恢复官方默认配置',
450
+ value: CLEAR_CODEX_CONFIG,
451
+ });
440
452
 
441
453
  var answers = await inquirer.prompt([{
442
454
  type: 'list',
@@ -445,19 +457,40 @@ async function setapiCx(cmd, buildCfg) {
445
457
  choices: choices,
446
458
  default: getCurrentCodexChannelId(auth, toml) || undefined,
447
459
  }]);
460
+ if (answers.channelId === CLEAR_CODEX_CONFIG) {
461
+ var clearAnswer = await inquirer.prompt([{
462
+ type: 'confirm',
463
+ name: 'confirmed',
464
+ message: '确认删除 Codex 配置文件并恢复官方默认配置吗?',
465
+ default: false,
466
+ }]);
467
+ if (!clearAnswer.confirmed) {
468
+ console.log('已取消清除 Codex 配置。');
469
+ return;
470
+ }
471
+ clearCodexConfig(buildCfg);
472
+ console.log('✅ 已清除 Codex 配置,客户端将使用官方默认配置。');
473
+ return;
474
+ }
475
+
476
+ if (!utils.CX_CHANNELS.length) {
477
+ throw new Error('Codex 渠道列表为空,请检查网络或服务端 cxChannels 配置');
478
+ }
479
+
448
480
  var selected = utils.CX_CHANNELS.find(function (c) { return c.id === answers.channelId; });
449
481
  var forcedKey = getEnvString(selected, 'OPENAI_API_KEY');
450
482
  var wireApi = getEnvString(selected, 'wire_api');
451
483
  var baseUrl = getEnvString(selected, 'base_url');
452
484
 
453
- // useSettingsFile 或自定义渠道允许手动指定 base_url;输入空白表示恢复 Codex 默认 provider
485
+ // useSettingsFile 或自定义渠道允许手动指定 base_url,空白输入由校验阻止。
454
486
  var allowManualBaseUrl = selected.useSettingsFile || String(selected.id || '').indexOf('custom') !== -1;
455
487
  if (allowManualBaseUrl) {
456
488
  var baseAnswer = await inquirer.prompt([{
457
489
  type: 'input',
458
490
  name: 'value',
459
- message: '请输入 Codex base_url(输入空格后回车恢复默认 provider):',
491
+ message: '请输入 Codex base_url:',
460
492
  default: baseUrl || getJcodexBaseUrl(toml) || '',
493
+ validate: validateRequiredInput,
461
494
  }]);
462
495
  baseUrl = baseAnswer.value.trim();
463
496
  }
@@ -466,8 +499,9 @@ async function setapiCx(cmd, buildCfg) {
466
499
  var keyAnswer = await inquirer.prompt([{
467
500
  type: 'input',
468
501
  name: 'value',
469
- message: '请输入 OPENAI_API_KEY(输入空格后回车删除该配置):',
502
+ message: '请输入 OPENAI_API_KEY:',
470
503
  default: getManualOpenAiKeyDefault(auth, selected.id),
504
+ validate: validateRequiredInput,
471
505
  }]);
472
506
  forcedKey = keyAnswer.value.trim();
473
507
  saveManualOpenAiKey(auth, selected.id, forcedKey);
@@ -478,19 +512,14 @@ async function setapiCx(cmd, buildCfg) {
478
512
  // 将选中渠道 id 持久化到 auth.__jid,作为 Codex 当前渠道标识
479
513
  auth.__jid = selected.id;
480
514
 
481
- // 手动 Key 输入为空时删除 OPENAI_API_KEY,便于恢复未配置状态
482
- if (forcedKey) {
483
- auth.OPENAI_API_KEY = forcedKey;
484
- } else {
485
- delete auth.OPENAI_API_KEY;
486
- }
515
+ // Key 来自渠道强制配置或已通过非空校验的手动输入,直接写入认证文件。
516
+ auth.OPENAI_API_KEY = forcedKey;
487
517
  utils.writeJsonFile(buildCfg.codexAuthPath, auth);
488
518
 
489
519
  if (baseUrl) {
490
- toml = setJcodexProvider(toml, baseUrl, wireApi);
520
+ toml = setJcodexProvider(toml, baseUrl, wireApi, forcedKey);
491
521
  } else {
492
522
  toml = removeTopLevelModelProvider(toml);
493
- toml = wireApi ? setJcodexWireApi(toml, wireApi) : removeJcodexWireApi(toml);
494
523
  }
495
524
  toml = ensureSandboxMode(toml);
496
525
  toml = removeEditorInsertNewlineKeymap(toml);
@@ -516,8 +545,6 @@ setapiCx._private = {
516
545
  removeEditorInsertNewlineKeymap: removeEditorInsertNewlineKeymap,
517
546
  removeTopLevelModelProvider: removeTopLevelModelProvider,
518
547
  removeJcodexProviderBlock: removeJcodexProviderBlock,
519
- removeJcodexWireApi: removeJcodexWireApi,
520
- setJcodexWireApi: setJcodexWireApi,
521
548
  setJcodexProvider: setJcodexProvider,
522
549
  getCurrentCodexChannelId: getCurrentCodexChannelId,
523
550
  saveLastManualOpenAiKey: saveLastManualOpenAiKey,
@@ -525,6 +552,9 @@ setapiCx._private = {
525
552
  saveManualOpenAiKey: saveManualOpenAiKey,
526
553
  resolveRequiredCodexVersion: resolveRequiredCodexVersion,
527
554
  ensureCodexVersion: ensureCodexVersion,
555
+ validateRequiredInput: validateRequiredInput,
556
+ clearCodexConfig: clearCodexConfig,
557
+ CLEAR_CODEX_CONFIG: CLEAR_CODEX_CONFIG,
528
558
  };
529
559
 
530
560
  module.exports = setapiCx;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "base_parts_ai",
3
- "version": "1.0.53",
3
+ "version": "1.0.55",
4
4
  "description": "jaskle base_parts_ai",
5
5
  "main": "./main.js",
6
6
  "registry": true,
@@ -31,8 +31,8 @@
31
31
  "author": "jaskle",
32
32
  "license": "ISC",
33
33
  "dependencies": {
34
+ "@ltd/j-toml": "^1.38.0",
34
35
  "commander": "^14.0.3",
35
- "inquirer": "^8.2.7",
36
- "smol-toml": "^1.6.1"
36
+ "inquirer": "^8.2.7"
37
37
  }
38
38
  }