base_parts_ai 1.0.46 → 1.0.48
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 +51 -6
- package/lib/setapi_cx.js +68 -9
- package/package.json +1 -1
- package/test_home/.codex/auth.json +5 -1
- package/test_home/.codex/config.toml +0 -3
- package/test_jcc.js +4 -0
- package/test_jcx.js +58 -9
package/lib/ai_utils.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
var fs = require('fs');
|
|
9
9
|
var path = require('path');
|
|
10
10
|
var http = require('http');
|
|
11
|
-
var { execSync } = require('child_process');
|
|
11
|
+
var { execSync, spawnSync } = require('child_process');
|
|
12
12
|
|
|
13
13
|
// ============================================================
|
|
14
14
|
// AI 渠道列表(运行时由 fetchConfig 从服务端拉取填充)
|
|
@@ -24,6 +24,51 @@ var noticeList = [];
|
|
|
24
24
|
// jcc.json 中保存最后一次手动输入 Token 的内部字段名
|
|
25
25
|
var LAST_MANUAL_AUTH_TOKEN_KEY = '__lastManualAuthToken';
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* 从当前入口文件名推断正在运行的 CLI 命令名
|
|
29
|
+
* @returns {string} jcc、jcx 或空字符串
|
|
30
|
+
*/
|
|
31
|
+
function getCurrentCliName() {
|
|
32
|
+
var entryName = path.basename(process.argv[1] || '').toLowerCase();
|
|
33
|
+
if (entryName === 'jcx' || entryName === 'jcx.js') return 'jcx';
|
|
34
|
+
if (entryName === 'jcc' || entryName === 'jcc.js') return 'jcc';
|
|
35
|
+
return '';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 升级完成后重新运行当前 CLI,并保留本次传入的全部参数
|
|
40
|
+
* @returns {void}
|
|
41
|
+
*/
|
|
42
|
+
function rerunCurrentCli() {
|
|
43
|
+
var cliName = getCurrentCliName();
|
|
44
|
+
if (!cliName) {
|
|
45
|
+
console.log('[fetchConfig] 升级完成,未识别当前 CLI,请重新运行');
|
|
46
|
+
process.exit(0);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 传递临时环境变量,避免新进程在版本缓存异常时反复触发升级。
|
|
50
|
+
var childEnv = Object.assign({}, process.env, {
|
|
51
|
+
BASE_PARTS_AI_SKIP_SELF_UPDATE: '1',
|
|
52
|
+
});
|
|
53
|
+
var args = process.argv.slice(2);
|
|
54
|
+
console.log('[fetchConfig] 升级完成,正在重新运行: ' + cliName + (args.length ? ' ' + args.join(' ') : ''));
|
|
55
|
+
var result = spawnSync(cliName, args, {
|
|
56
|
+
stdio: 'inherit',
|
|
57
|
+
shell: process.platform === 'win32',
|
|
58
|
+
env: childEnv,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (result.error) {
|
|
62
|
+
console.warn('[fetchConfig] 重新运行失败:', result.error.message);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
if (result.signal) {
|
|
66
|
+
console.warn('[fetchConfig] 重新运行被信号中断:', result.signal);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
process.exit(typeof result.status === 'number' ? result.status : 0);
|
|
70
|
+
}
|
|
71
|
+
|
|
27
72
|
/**
|
|
28
73
|
* 从服务端拉取渠道配置,并检查 jcc 自身是否需要升级
|
|
29
74
|
* @param {object} buildCfg getBuildCfg 返回的配置对象
|
|
@@ -76,7 +121,7 @@ function fetchConfig(buildCfg) {
|
|
|
76
121
|
}
|
|
77
122
|
return false;
|
|
78
123
|
}
|
|
79
|
-
if (newVer && semverGt(newVer, currentVer)) {
|
|
124
|
+
if (newVer && process.env.BASE_PARTS_AI_SKIP_SELF_UPDATE !== '1' && semverGt(newVer, currentVer)) {
|
|
80
125
|
console.log('[fetchConfig] 检测到新版本 jcc: ' + newVer + '(当前 ' + currentVer + ')');
|
|
81
126
|
var confirmed = "yes"
|
|
82
127
|
// confirmed = await select({
|
|
@@ -93,8 +138,7 @@ function fetchConfig(buildCfg) {
|
|
|
93
138
|
try {
|
|
94
139
|
var tgzUrl = 'https://jdwfiles.oss-cn-hangzhou.aliyuncs.com/npm_pkg/base_parts_ai-' + newVer + '.tgz';
|
|
95
140
|
execSync('npm install -g ' + tgzUrl + ' --force --registry=https://registry.npmmirror.com', { stdio: 'inherit' });
|
|
96
|
-
|
|
97
|
-
process.exit(0);
|
|
141
|
+
rerunCurrentCli();
|
|
98
142
|
} catch (e) {
|
|
99
143
|
console.warn('[fetchConfig] 自动升级失败:', e.message);
|
|
100
144
|
}
|
|
@@ -262,7 +306,7 @@ function getLastManualAuthToken(jccJson) {
|
|
|
262
306
|
|
|
263
307
|
/**
|
|
264
308
|
* 计算需要提示用户输入 Token 时的默认值
|
|
265
|
-
*
|
|
309
|
+
* 优先使用当前渠道缓存;只有该渠道无缓存时,才使用当前渠道配置或最后一次手动 Token 兜底
|
|
266
310
|
* @param {object} jccJson jcc.json 对象
|
|
267
311
|
* @param {string} channelId 当前渠道 id
|
|
268
312
|
* @param {object} settings Claude settings.json 对象
|
|
@@ -271,7 +315,8 @@ function getLastManualAuthToken(jccJson) {
|
|
|
271
315
|
function getManualAuthTokenDefault(jccJson, channelId, settings) {
|
|
272
316
|
var cachedToken = (jccJson.keys && jccJson.keys[channelId]) || '';
|
|
273
317
|
var currentToken = (settings.env && settings.env.ANTHROPIC_AUTH_TOKEN) || '';
|
|
274
|
-
|
|
318
|
+
var currentChannelToken = (!settings.__jid || settings.__jid === channelId) ? currentToken : '';
|
|
319
|
+
return cachedToken || currentChannelToken || getLastManualAuthToken(jccJson) || currentToken;
|
|
275
320
|
}
|
|
276
321
|
|
|
277
322
|
/**
|
package/lib/setapi_cx.js
CHANGED
|
@@ -152,16 +152,29 @@ function ensureSandboxMode(toml) {
|
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
/**
|
|
155
|
-
*
|
|
155
|
+
* 强制删除 Codex TUI 编辑器 insert_newline 配置,交给官方默认行为处理
|
|
156
156
|
* @param {string} toml TOML 内容
|
|
157
157
|
* @returns {string}
|
|
158
158
|
*/
|
|
159
|
-
function
|
|
159
|
+
function removeEditorInsertNewlineKeymap(toml) {
|
|
160
160
|
var config = parseTomlConfig(toml);
|
|
161
|
-
var tui =
|
|
162
|
-
var keymap =
|
|
163
|
-
var editor =
|
|
164
|
-
|
|
161
|
+
var tui = config.tui;
|
|
162
|
+
var keymap = tui && typeof tui === 'object' && !Array.isArray(tui) ? tui.keymap : null;
|
|
163
|
+
var editor = keymap && typeof keymap === 'object' && !Array.isArray(keymap) ? keymap.editor : null;
|
|
164
|
+
|
|
165
|
+
// 只删除官方已支持的 insert_newline,保留用户其它 TUI 快捷键配置。
|
|
166
|
+
if (editor && typeof editor === 'object' && !Array.isArray(editor)) {
|
|
167
|
+
delete editor.insert_newline;
|
|
168
|
+
if (isEmptyObject(editor)) {
|
|
169
|
+
delete keymap.editor;
|
|
170
|
+
}
|
|
171
|
+
if (isEmptyObject(keymap)) {
|
|
172
|
+
delete tui.keymap;
|
|
173
|
+
}
|
|
174
|
+
if (isEmptyObject(tui)) {
|
|
175
|
+
delete config.tui;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
165
178
|
return stringifyTomlConfig(config);
|
|
166
179
|
}
|
|
167
180
|
|
|
@@ -356,6 +369,48 @@ function getCurrentCodexChannelId(auth, toml) {
|
|
|
356
369
|
return channel ? channel.id : '';
|
|
357
370
|
}
|
|
358
371
|
|
|
372
|
+
/**
|
|
373
|
+
* 记录最后一次由用户手动输入的 Codex Key
|
|
374
|
+
* 该值仅作为新渠道无专属 Key 时的默认输入提示
|
|
375
|
+
* @param {object} auth auth.json 对象
|
|
376
|
+
* @param {string} key 用户手动输入的 OPENAI_API_KEY
|
|
377
|
+
*/
|
|
378
|
+
function saveLastManualOpenAiKey(auth, key) {
|
|
379
|
+
if (key) {
|
|
380
|
+
auth.__lastManualOpenAiKey = key;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* 获取 Codex 手动 Key 输入框默认值
|
|
386
|
+
* 优先使用当前渠道专属缓存;没有缓存时才用当前渠道 Key 或最后手动 Key 兜底
|
|
387
|
+
* @param {object} auth auth.json 对象
|
|
388
|
+
* @param {string} channelId 当前选择的渠道 id
|
|
389
|
+
* @returns {string}
|
|
390
|
+
*/
|
|
391
|
+
function getManualOpenAiKeyDefault(auth, channelId) {
|
|
392
|
+
var cachedKey = (auth.__keys && auth.__keys[channelId]) || '';
|
|
393
|
+
var currentKey = auth.OPENAI_API_KEY || '';
|
|
394
|
+
var currentChannelKey = (!auth.__jid || auth.__jid === channelId) ? currentKey : '';
|
|
395
|
+
return cachedKey || currentChannelKey || auth.__lastManualOpenAiKey || currentKey;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* 保存或删除 Codex 渠道专属 Key 缓存
|
|
400
|
+
* @param {object} auth auth.json 对象
|
|
401
|
+
* @param {string} channelId 当前选择的渠道 id
|
|
402
|
+
* @param {string} key 用户手动输入的 OPENAI_API_KEY,空值表示删除该渠道缓存
|
|
403
|
+
*/
|
|
404
|
+
function saveManualOpenAiKey(auth, channelId, key) {
|
|
405
|
+
if (!auth.__keys) { auth.__keys = {}; }
|
|
406
|
+
if (key) {
|
|
407
|
+
auth.__keys[channelId] = key;
|
|
408
|
+
saveLastManualOpenAiKey(auth, key);
|
|
409
|
+
} else {
|
|
410
|
+
delete auth.__keys[channelId];
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
359
414
|
/**
|
|
360
415
|
* 执行 Codex setapi 交互配置
|
|
361
416
|
* @param {object} cmd 命令参数
|
|
@@ -407,9 +462,10 @@ async function setapiCx(cmd, buildCfg) {
|
|
|
407
462
|
type: 'input',
|
|
408
463
|
name: 'value',
|
|
409
464
|
message: '请输入 OPENAI_API_KEY(输入空格后回车删除该配置):',
|
|
410
|
-
default: auth.
|
|
465
|
+
default: getManualOpenAiKeyDefault(auth, selected.id),
|
|
411
466
|
}]);
|
|
412
467
|
forcedKey = keyAnswer.value.trim();
|
|
468
|
+
saveManualOpenAiKey(auth, selected.id, forcedKey);
|
|
413
469
|
} else {
|
|
414
470
|
console.log('已使用渠道配置中的 OPENAI_API_KEY,跳过 Key 输入。');
|
|
415
471
|
}
|
|
@@ -432,7 +488,7 @@ async function setapiCx(cmd, buildCfg) {
|
|
|
432
488
|
toml = wireApi ? setJcodexWireApi(toml, wireApi) : removeJcodexWireApi(toml);
|
|
433
489
|
}
|
|
434
490
|
toml = ensureSandboxMode(toml);
|
|
435
|
-
toml =
|
|
491
|
+
toml = removeEditorInsertNewlineKeymap(toml);
|
|
436
492
|
writeTextFile(buildCfg.codexConfigPath, toml);
|
|
437
493
|
|
|
438
494
|
await refreshAgentsFile(buildCfg.codexAgentsPath);
|
|
@@ -452,13 +508,16 @@ setapiCx._private = {
|
|
|
452
508
|
escapeTomlString: escapeTomlString,
|
|
453
509
|
hasTopLevelKey: hasTopLevelKey,
|
|
454
510
|
ensureSandboxMode: ensureSandboxMode,
|
|
455
|
-
|
|
511
|
+
removeEditorInsertNewlineKeymap: removeEditorInsertNewlineKeymap,
|
|
456
512
|
removeTopLevelModelProvider: removeTopLevelModelProvider,
|
|
457
513
|
removeJcodexProviderBlock: removeJcodexProviderBlock,
|
|
458
514
|
removeJcodexWireApi: removeJcodexWireApi,
|
|
459
515
|
setJcodexWireApi: setJcodexWireApi,
|
|
460
516
|
setJcodexProvider: setJcodexProvider,
|
|
461
517
|
getCurrentCodexChannelId: getCurrentCodexChannelId,
|
|
518
|
+
saveLastManualOpenAiKey: saveLastManualOpenAiKey,
|
|
519
|
+
getManualOpenAiKeyDefault: getManualOpenAiKeyDefault,
|
|
520
|
+
saveManualOpenAiKey: saveManualOpenAiKey,
|
|
462
521
|
resolveRequiredCodexVersion: resolveRequiredCodexVersion,
|
|
463
522
|
ensureCodexVersion: ensureCodexVersion,
|
|
464
523
|
};
|
package/package.json
CHANGED
package/test_jcc.js
CHANGED
|
@@ -378,12 +378,15 @@ console.log('\n======= Test 10: Token 默认值与强制配置 =======');
|
|
|
378
378
|
|
|
379
379
|
settings.env.ANTHROPIC_AUTH_TOKEN = 'sk-current-settings';
|
|
380
380
|
var currentDefault = utils.getManualAuthTokenDefault({ keys: {} }, 'channel_c', settings);
|
|
381
|
+
settings.__jid = 'other_channel';
|
|
382
|
+
var otherChannelDefault = utils.getManualAuthTokenDefault(jccJson, 'channel_b', settings);
|
|
381
383
|
|
|
382
384
|
console.log('强制 Token:', forcedToken);
|
|
383
385
|
console.log('空强制 Token:', emptyForcedToken || '(空)');
|
|
384
386
|
console.log('渠道缓存默认值:', cachedDefault);
|
|
385
387
|
console.log('最后手动默认值:', lastManualDefault);
|
|
386
388
|
console.log('当前 settings 默认值:', currentDefault);
|
|
389
|
+
console.log('其它渠道 settings 兜底默认值:', otherChannelDefault);
|
|
387
390
|
|
|
388
391
|
var ok = true;
|
|
389
392
|
ok = ok && (forcedToken === 'sk-forced-token');
|
|
@@ -391,6 +394,7 @@ console.log('\n======= Test 10: Token 默认值与强制配置 =======');
|
|
|
391
394
|
ok = ok && (cachedDefault === 'sk-channel-cache');
|
|
392
395
|
ok = ok && (lastManualDefault === 'sk-last-manual');
|
|
393
396
|
ok = ok && (currentDefault === 'sk-current-settings');
|
|
397
|
+
ok = ok && (otherChannelDefault === 'sk-last-manual');
|
|
394
398
|
|
|
395
399
|
console.log('✅ Test 10 通过:', ok);
|
|
396
400
|
if (!ok) { process.exit(1); }
|
package/test_jcx.js
CHANGED
|
@@ -173,18 +173,18 @@ async function runTests() {
|
|
|
173
173
|
})();
|
|
174
174
|
|
|
175
175
|
// ============================================================
|
|
176
|
-
// 测试 2.2
|
|
176
|
+
// 测试 2.2:强制删除 TUI 编辑器换行快捷键
|
|
177
177
|
// ============================================================
|
|
178
|
-
console.log('\n======= jcx Test 2.2:
|
|
178
|
+
console.log('\n======= jcx Test 2.2: 强制删除 TUI keymap insert_newline =======');
|
|
179
179
|
(function () {
|
|
180
|
-
var original = '[tui]\ntheme = "dark"\n\n[tui.keymap.editor]\ninsert_newline = "enter"\n';
|
|
181
|
-
var updated = privateApi.
|
|
180
|
+
var original = '[tui]\ntheme = "dark"\n\n[tui.keymap.editor]\ninsert_newline = "enter"\naccept_completion = "tab"\n';
|
|
181
|
+
var updated = privateApi.removeEditorInsertNewlineKeymap(original);
|
|
182
182
|
var ok = true;
|
|
183
183
|
ok = ok && updated.indexOf('[tui]') !== -1;
|
|
184
184
|
ok = ok && updated.indexOf('theme = "dark"') !== -1;
|
|
185
185
|
ok = ok && updated.indexOf('[tui.keymap.editor]') !== -1;
|
|
186
|
-
ok = ok && updated.indexOf('
|
|
187
|
-
ok = ok && updated.indexOf('insert_newline
|
|
186
|
+
ok = ok && updated.indexOf('accept_completion = "tab"') !== -1;
|
|
187
|
+
ok = ok && updated.indexOf('insert_newline') === -1;
|
|
188
188
|
console.log('✅ Test 2.2 通过:', ok);
|
|
189
189
|
if (!ok) { process.exit(1); }
|
|
190
190
|
})();
|
|
@@ -235,8 +235,7 @@ async function runTests() {
|
|
|
235
235
|
ok = ok && toml.indexOf('sandbox_mode = "danger-full-access"') !== -1;
|
|
236
236
|
ok = ok && toml.indexOf('base_url = "https://forced.example.com/v1"') !== -1;
|
|
237
237
|
ok = ok && toml.indexOf('wire_api = "wire-forced-provider-key"') !== -1;
|
|
238
|
-
ok = ok && toml.indexOf('
|
|
239
|
-
ok = ok && toml.indexOf('insert_newline = "alt-enter"') !== -1;
|
|
238
|
+
ok = ok && toml.indexOf('insert_newline') === -1;
|
|
240
239
|
ok = ok && agents.indexOf('cli=cx') !== -1;
|
|
241
240
|
ok = ok && agents.indexOf('cli=cx-' + utils.getAiTipOsParam()) !== -1;
|
|
242
241
|
console.log('✅ Test 4 通过:', ok);
|
|
@@ -287,7 +286,12 @@ async function runTests() {
|
|
|
287
286
|
console.log('\n======= jcx Test 6: 可删除输入框保留默认值 =======');
|
|
288
287
|
await withMockFetchText(async function () {
|
|
289
288
|
writeText(buildCfg.codexConfigPath, 'model_provider = "jcodex"\n\n[model_providers.jcodex]\nname = "jcodex"\nbase_url = "https://old.example.com/v1"\n');
|
|
290
|
-
utils.writeJsonFile(buildCfg.codexAuthPath, {
|
|
289
|
+
utils.writeJsonFile(buildCfg.codexAuthPath, {
|
|
290
|
+
OPENAI_API_KEY: 'sk-old-key',
|
|
291
|
+
__keys: {
|
|
292
|
+
manual_channel: 'sk-old-key',
|
|
293
|
+
},
|
|
294
|
+
});
|
|
291
295
|
utils.CX_CHANNELS.length = 0;
|
|
292
296
|
utils.CX_CHANNELS.push({
|
|
293
297
|
id: 'manual_channel',
|
|
@@ -323,6 +327,51 @@ async function runTests() {
|
|
|
323
327
|
if (!ok) { process.exit(1); }
|
|
324
328
|
});
|
|
325
329
|
|
|
330
|
+
// ============================================================
|
|
331
|
+
// 测试 6.1:Codex Key 按渠道缓存,当前其它渠道 Key 不覆盖专属默认值
|
|
332
|
+
// ============================================================
|
|
333
|
+
console.log('\n======= jcx Test 6.1: Codex Key 按渠道缓存 =======');
|
|
334
|
+
await withMockFetchText(async function () {
|
|
335
|
+
writeText(buildCfg.codexConfigPath, 'sandbox_mode = "danger-full-access"\n');
|
|
336
|
+
utils.writeJsonFile(buildCfg.codexAuthPath, {
|
|
337
|
+
__jid: 'channel_a',
|
|
338
|
+
OPENAI_API_KEY: 'sk-current-channel-a',
|
|
339
|
+
__lastManualOpenAiKey: 'sk-last-manual',
|
|
340
|
+
__keys: {
|
|
341
|
+
channel_b: 'sk-channel-b-cache',
|
|
342
|
+
},
|
|
343
|
+
});
|
|
344
|
+
utils.CX_CHANNELS.length = 0;
|
|
345
|
+
utils.CX_CHANNELS.push({
|
|
346
|
+
id: 'channel_b',
|
|
347
|
+
name: 'Channel B',
|
|
348
|
+
description: 'manual key cache channel',
|
|
349
|
+
useSettingsFile: false,
|
|
350
|
+
envObj: {
|
|
351
|
+
base_url: 'https://channel-b.example.com/v1',
|
|
352
|
+
},
|
|
353
|
+
});
|
|
354
|
+
var captured = [];
|
|
355
|
+
await withCapturedPrompts([
|
|
356
|
+
{ channelId: 'channel_b' },
|
|
357
|
+
{ value: 'sk-channel-b-new' },
|
|
358
|
+
], captured, async function () {
|
|
359
|
+
await setapiCx({}, buildCfg);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
var auth = JSON.parse(readText(buildCfg.codexAuthPath));
|
|
363
|
+
var ok = true;
|
|
364
|
+
ok = ok && captured.length === 2;
|
|
365
|
+
ok = ok && captured[1].message.indexOf('OPENAI_API_KEY') !== -1;
|
|
366
|
+
ok = ok && captured[1].default === 'sk-channel-b-cache';
|
|
367
|
+
ok = ok && auth.__jid === 'channel_b';
|
|
368
|
+
ok = ok && auth.OPENAI_API_KEY === 'sk-channel-b-new';
|
|
369
|
+
ok = ok && auth.__keys.channel_b === 'sk-channel-b-new';
|
|
370
|
+
ok = ok && auth.__lastManualOpenAiKey === 'sk-channel-b-new';
|
|
371
|
+
console.log('✅ Test 6.1 通过:', ok);
|
|
372
|
+
if (!ok) { process.exit(1); }
|
|
373
|
+
});
|
|
374
|
+
|
|
326
375
|
// ============================================================
|
|
327
376
|
// 测试 7:base_url 为空时仍强制写入非空 wire_api
|
|
328
377
|
// ============================================================
|