base_parts_ai 1.0.53 → 1.0.54

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
@@ -12,6 +12,32 @@ var utils = require('./ai_utils');
12
12
  var childProcess = require('child_process');
13
13
  var TOML = require('smol-toml');
14
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
+ }
40
+
15
41
  /**
16
42
  * 读取文本文件,文件不存在时返回空字符串
17
43
  * @param {string} filePath 文件路径
@@ -71,6 +97,11 @@ function parseTomlConfig(toml) {
71
97
  */
72
98
  function stringifyTomlConfig(config) {
73
99
  var content = TOML.stringify(config || {});
100
+ // smol-toml 默认把嵌套对象输出为子表;此固定请求头需按 Codex 支持的内联表格式写入。
101
+ content = content.replace(
102
+ /\n\[model_providers\.jcodex\.http_headers\]\n(?:"?x-openai-actor-authorization"?) = "local-image-extension"\n/g,
103
+ '\nhttp_headers = { "x-openai-actor-authorization" = "local-image-extension" }\n'
104
+ );
74
105
  return content ? content.replace(/\s+$/, '') + '\n' : '';
75
106
  }
76
107
 
@@ -219,6 +250,11 @@ function setJcodexProvider(toml, baseUrl, wireApi) {
219
250
  providers.jcodex = {
220
251
  name: 'jcodex',
221
252
  base_url: baseUrl,
253
+ // jcodex 使用本地图片扩展身份,不要求 Codex 注入 OpenAI 鉴权。
254
+ requires_openai_auth: false,
255
+ http_headers: {
256
+ 'x-openai-actor-authorization': 'local-image-extension',
257
+ },
222
258
  };
223
259
  // wire_api 是 Codex provider 私有字段,非空时强制写入到 model_providers.jcodex 下
224
260
  if (wireApi) {
@@ -255,6 +291,11 @@ function setJcodexWireApi(toml, wireApi) {
255
291
  if (!provider.name) {
256
292
  provider.name = 'jcodex';
257
293
  }
294
+ // 即使渠道只配置 wire_api,也要补齐 jcodex 的固定鉴权参数。
295
+ provider.requires_openai_auth = false;
296
+ provider.http_headers = {
297
+ 'x-openai-actor-authorization': 'local-image-extension',
298
+ };
258
299
  provider.wire_api = wireApi;
259
300
  return stringifyTomlConfig(config);
260
301
  }
@@ -427,16 +468,17 @@ async function setapiCx(cmd, buildCfg) {
427
468
 
428
469
  printCurrentCodexInfo(auth, toml);
429
470
 
430
- if (!utils.CX_CHANNELS.length) {
431
- throw new Error('Codex 渠道列表为空,请检查网络或服务端 cxChannels 配置');
432
- }
433
-
434
471
  var choices = utils.CX_CHANNELS.map(function (c) {
435
472
  return {
436
473
  name: c.name + ' — ' + (c.description || ''),
437
474
  value: c.id,
438
475
  };
439
476
  });
477
+ // 清除入口固定放在渠道列表最后,即使服务端渠道为空也可恢复官方配置。
478
+ choices.push({
479
+ name: '>>>>清除Codex配置<<<< — 删除并恢复官方默认配置',
480
+ value: CLEAR_CODEX_CONFIG,
481
+ });
440
482
 
441
483
  var answers = await inquirer.prompt([{
442
484
  type: 'list',
@@ -445,19 +487,40 @@ async function setapiCx(cmd, buildCfg) {
445
487
  choices: choices,
446
488
  default: getCurrentCodexChannelId(auth, toml) || undefined,
447
489
  }]);
490
+ if (answers.channelId === CLEAR_CODEX_CONFIG) {
491
+ var clearAnswer = await inquirer.prompt([{
492
+ type: 'confirm',
493
+ name: 'confirmed',
494
+ message: '确认删除 Codex 配置文件并恢复官方默认配置吗?',
495
+ default: false,
496
+ }]);
497
+ if (!clearAnswer.confirmed) {
498
+ console.log('已取消清除 Codex 配置。');
499
+ return;
500
+ }
501
+ clearCodexConfig(buildCfg);
502
+ console.log('✅ 已清除 Codex 配置,客户端将使用官方默认配置。');
503
+ return;
504
+ }
505
+
506
+ if (!utils.CX_CHANNELS.length) {
507
+ throw new Error('Codex 渠道列表为空,请检查网络或服务端 cxChannels 配置');
508
+ }
509
+
448
510
  var selected = utils.CX_CHANNELS.find(function (c) { return c.id === answers.channelId; });
449
511
  var forcedKey = getEnvString(selected, 'OPENAI_API_KEY');
450
512
  var wireApi = getEnvString(selected, 'wire_api');
451
513
  var baseUrl = getEnvString(selected, 'base_url');
452
514
 
453
- // useSettingsFile 或自定义渠道允许手动指定 base_url;输入空白表示恢复 Codex 默认 provider
515
+ // useSettingsFile 或自定义渠道允许手动指定 base_url,空白输入由校验阻止。
454
516
  var allowManualBaseUrl = selected.useSettingsFile || String(selected.id || '').indexOf('custom') !== -1;
455
517
  if (allowManualBaseUrl) {
456
518
  var baseAnswer = await inquirer.prompt([{
457
519
  type: 'input',
458
520
  name: 'value',
459
- message: '请输入 Codex base_url(输入空格后回车恢复默认 provider):',
521
+ message: '请输入 Codex base_url:',
460
522
  default: baseUrl || getJcodexBaseUrl(toml) || '',
523
+ validate: validateRequiredInput,
461
524
  }]);
462
525
  baseUrl = baseAnswer.value.trim();
463
526
  }
@@ -466,8 +529,9 @@ async function setapiCx(cmd, buildCfg) {
466
529
  var keyAnswer = await inquirer.prompt([{
467
530
  type: 'input',
468
531
  name: 'value',
469
- message: '请输入 OPENAI_API_KEY(输入空格后回车删除该配置):',
532
+ message: '请输入 OPENAI_API_KEY:',
470
533
  default: getManualOpenAiKeyDefault(auth, selected.id),
534
+ validate: validateRequiredInput,
471
535
  }]);
472
536
  forcedKey = keyAnswer.value.trim();
473
537
  saveManualOpenAiKey(auth, selected.id, forcedKey);
@@ -478,12 +542,8 @@ async function setapiCx(cmd, buildCfg) {
478
542
  // 将选中渠道 id 持久化到 auth.__jid,作为 Codex 当前渠道标识
479
543
  auth.__jid = selected.id;
480
544
 
481
- // 手动 Key 输入为空时删除 OPENAI_API_KEY,便于恢复未配置状态
482
- if (forcedKey) {
483
- auth.OPENAI_API_KEY = forcedKey;
484
- } else {
485
- delete auth.OPENAI_API_KEY;
486
- }
545
+ // Key 来自渠道强制配置或已通过非空校验的手动输入,直接写入认证文件。
546
+ auth.OPENAI_API_KEY = forcedKey;
487
547
  utils.writeJsonFile(buildCfg.codexAuthPath, auth);
488
548
 
489
549
  if (baseUrl) {
@@ -525,6 +585,9 @@ setapiCx._private = {
525
585
  saveManualOpenAiKey: saveManualOpenAiKey,
526
586
  resolveRequiredCodexVersion: resolveRequiredCodexVersion,
527
587
  ensureCodexVersion: ensureCodexVersion,
588
+ validateRequiredInput: validateRequiredInput,
589
+ clearCodexConfig: clearCodexConfig,
590
+ CLEAR_CODEX_CONFIG: CLEAR_CODEX_CONFIG,
528
591
  };
529
592
 
530
593
  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.54",
4
4
  "description": "jaskle base_parts_ai",
5
5
  "main": "./main.js",
6
6
  "registry": true,