base_parts_ai 1.0.44 → 1.0.46

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 CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
2
  ## 下载发布的包
3
- https://registry.npmjs.org/base_parts_ai/-/base_parts_ai-1.0.43.tgz
3
+ https://registry.npmjs.org/base_parts_ai/-/base_parts_ai-1.0.45.tgz
4
4
 
5
5
  ## 然后放在jdwfiles目录下
6
6
  https://jdwfiles.oss-cn-hangzhou.aliyuncs.com/npm_pkg/base_parts_ai-1.0.34.tgz
package/lib/setapi_cx.js CHANGED
@@ -10,6 +10,7 @@ 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
14
 
14
15
  /**
15
16
  * 读取文本文件,文件不存在时返回空字符串
@@ -47,27 +48,63 @@ function getEnvString(channel, key) {
47
48
  return typeof value === 'string' ? value.trim() : '';
48
49
  }
49
50
 
51
+ /**
52
+ * 解析 Codex TOML 配置,格式错误时阻止继续覆盖用户配置
53
+ * @param {string} toml TOML 内容
54
+ * @returns {object}
55
+ */
56
+ function parseTomlConfig(toml) {
57
+ if (!toml || !String(toml).trim()) {
58
+ return {};
59
+ }
60
+ try {
61
+ return TOML.parse(toml);
62
+ } catch (e) {
63
+ throw new Error('Codex config.toml 格式错误,已停止写入以避免破坏配置: ' + e.message);
64
+ }
65
+ }
66
+
67
+ /**
68
+ * 将 Codex TOML 配置对象序列化为文本
69
+ * @param {object} config TOML 配置对象
70
+ * @returns {string}
71
+ */
72
+ function stringifyTomlConfig(config) {
73
+ var content = TOML.stringify(config || {});
74
+ return content ? content.replace(/\s+$/, '') + '\n' : '';
75
+ }
76
+
77
+ /**
78
+ * 确保对象字段是可写 TOML table
79
+ * @param {object} parent 父级对象
80
+ * @param {string} key 字段名
81
+ * @returns {object}
82
+ */
83
+ function ensureTable(parent, key) {
84
+ if (!parent[key] || typeof parent[key] !== 'object' || Array.isArray(parent[key])) {
85
+ // 字段不是 table 时用对象替换,避免生成非法的嵌套配置
86
+ parent[key] = {};
87
+ }
88
+ return parent[key];
89
+ }
90
+
91
+ /**
92
+ * 判断对象是否没有自有字段
93
+ * @param {object} value 待检查对象
94
+ * @returns {boolean}
95
+ */
96
+ function isEmptyObject(value) {
97
+ return value && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 0;
98
+ }
99
+
50
100
  /**
51
101
  * 从 TOML 文本中提取顶层 model_provider
52
102
  * @param {string} toml TOML 内容
53
103
  * @returns {string}
54
104
  */
55
105
  function getTopLevelModelProvider(toml) {
56
- var lines = toml.split(/\r?\n/);
57
- var inSection = false;
58
- for (var i = 0; i < lines.length; i++) {
59
- var line = lines[i];
60
- if (/^\s*\[/.test(line)) {
61
- inSection = true;
62
- }
63
- if (!inSection) {
64
- var match = line.match(/^\s*model_provider\s*=\s*["']([^"']+)["']/);
65
- if (match) {
66
- return match[1];
67
- }
68
- }
69
- }
70
- return '';
106
+ var config = parseTomlConfig(toml);
107
+ return typeof config.model_provider === 'string' ? config.model_provider : '';
71
108
  }
72
109
 
73
110
  /**
@@ -76,26 +113,13 @@ function getTopLevelModelProvider(toml) {
76
113
  * @returns {string}
77
114
  */
78
115
  function getJcodexBaseUrl(toml) {
79
- var lines = toml.split(/\r?\n/);
80
- var inJcodex = false;
81
- for (var i = 0; i < lines.length; i++) {
82
- var line = lines[i];
83
- if (/^\s*\[/.test(line)) {
84
- inJcodex = /^\s*\[model_providers\.jcodex\]\s*$/.test(line);
85
- continue;
86
- }
87
- if (inJcodex) {
88
- var match = line.match(/^\s*base_url\s*=\s*["']([^"']*)["']/);
89
- if (match) {
90
- return match[1];
91
- }
92
- }
93
- }
94
- return '';
116
+ var config = parseTomlConfig(toml);
117
+ var provider = config.model_providers && config.model_providers.jcodex;
118
+ return provider && typeof provider.base_url === 'string' ? provider.base_url : '';
95
119
  }
96
120
 
97
121
  /**
98
- * 转义 TOML 字符串值,避免引号和反斜杠破坏配置格式
122
+ * 转义 TOML 字符串值,保留给测试和旧调用方使用
99
123
  * @param {string} value 原始字符串
100
124
  * @returns {string}
101
125
  */
@@ -103,25 +127,53 @@ function escapeTomlString(value) {
103
127
  return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
104
128
  }
105
129
 
130
+ /**
131
+ * 判断 TOML 顶层是否已经存在指定配置项
132
+ * @param {string} toml TOML 内容
133
+ * @param {string} key 顶层配置项名称
134
+ * @returns {boolean}
135
+ */
136
+ function hasTopLevelKey(toml, key) {
137
+ var config = parseTomlConfig(toml);
138
+ return Object.prototype.hasOwnProperty.call(config, key);
139
+ }
140
+
141
+ /**
142
+ * 确保 Codex 配置包含默认 sandbox_mode,缺失时写入顶层配置
143
+ * @param {string} toml TOML 内容
144
+ * @returns {string}
145
+ */
146
+ function ensureSandboxMode(toml) {
147
+ var config = parseTomlConfig(toml);
148
+ if (!Object.prototype.hasOwnProperty.call(config, 'sandbox_mode')) {
149
+ config.sandbox_mode = 'danger-full-access';
150
+ }
151
+ return stringifyTomlConfig(config);
152
+ }
153
+
154
+ /**
155
+ * 强制写入 Codex TUI 编辑器换行快捷键,已有值会被覆盖为 alt-enter
156
+ * @param {string} toml TOML 内容
157
+ * @returns {string}
158
+ */
159
+ function ensureEditorInsertNewlineKeymap(toml) {
160
+ var config = parseTomlConfig(toml);
161
+ var tui = ensureTable(config, 'tui');
162
+ var keymap = ensureTable(tui, 'keymap');
163
+ var editor = ensureTable(keymap, 'editor');
164
+ editor.insert_newline = 'alt-enter';
165
+ return stringifyTomlConfig(config);
166
+ }
167
+
106
168
  /**
107
169
  * 删除顶层 model_provider 字段,保留其它配置段落
108
170
  * @param {string} toml TOML 内容
109
171
  * @returns {string}
110
172
  */
111
173
  function removeTopLevelModelProvider(toml) {
112
- var lines = toml.split(/\r?\n/);
113
- var inSection = false;
114
- var kept = [];
115
- lines.forEach(function (line) {
116
- if (/^\s*\[/.test(line)) {
117
- inSection = true;
118
- }
119
- if (!inSection && /^\s*model_provider\s*=/.test(line)) {
120
- return;
121
- }
122
- kept.push(line);
123
- });
124
- return kept.join('\n').replace(/\n{3,}/g, '\n\n').replace(/\s+$/, '') + '\n';
174
+ var config = parseTomlConfig(toml);
175
+ delete config.model_provider;
176
+ return stringifyTomlConfig(config);
125
177
  }
126
178
 
127
179
  /**
@@ -130,22 +182,14 @@ function removeTopLevelModelProvider(toml) {
130
182
  * @returns {string}
131
183
  */
132
184
  function removeJcodexProviderBlock(toml) {
133
- var lines = toml.split(/\r?\n/);
134
- var kept = [];
135
- var skipping = false;
136
- lines.forEach(function (line) {
137
- if (/^\s*\[model_providers\.jcodex\]\s*$/.test(line)) {
138
- skipping = true;
139
- return;
140
- }
141
- if (skipping && /^\s*\[/.test(line)) {
142
- skipping = false;
185
+ var config = parseTomlConfig(toml);
186
+ if (config.model_providers && typeof config.model_providers === 'object' && !Array.isArray(config.model_providers)) {
187
+ delete config.model_providers.jcodex;
188
+ if (isEmptyObject(config.model_providers)) {
189
+ delete config.model_providers;
143
190
  }
144
- if (!skipping) {
145
- kept.push(line);
146
- }
147
- });
148
- return kept.join('\n').replace(/\n{3,}/g, '\n\n').replace(/\s+$/, '');
191
+ }
192
+ return stringifyTomlConfig(config);
149
193
  }
150
194
 
151
195
  /**
@@ -156,44 +200,18 @@ function removeJcodexProviderBlock(toml) {
156
200
  * @returns {string}
157
201
  */
158
202
  function setJcodexProvider(toml, baseUrl, wireApi) {
159
- var withoutProvider = removeJcodexProviderBlock(toml);
160
- var lines = withoutProvider.split(/\r?\n/);
161
- var inserted = false;
162
- var result = [];
163
-
164
- lines.forEach(function (line) {
165
- if (!inserted && /^\s*model_provider\s*=/.test(line)) {
166
- result.push('model_provider = "jcodex"');
167
- inserted = true;
168
- return;
169
- }
170
- if (!inserted && /^\s*\[/.test(line)) {
171
- result.push('model_provider = "jcodex"');
172
- result.push('');
173
- inserted = true;
174
- }
175
- result.push(line);
176
- });
177
-
178
- if (!inserted) {
179
- if (result.length && result[result.length - 1].trim() !== '') {
180
- result.push('');
181
- }
182
- result.unshift('model_provider = "jcodex"');
183
- }
184
-
185
- var body = result.join('\n').replace(/\n{3,}/g, '\n\n').replace(/\s+$/, '');
186
- if (body) {
187
- body += '\n\n';
188
- }
189
- body += '[model_providers.jcodex]\n';
190
- body += 'name = "jcodex"\n';
191
- body += 'base_url = "' + escapeTomlString(baseUrl) + '"\n';
203
+ var config = parseTomlConfig(toml);
204
+ var providers = ensureTable(config, 'model_providers');
205
+ config.model_provider = 'jcodex';
206
+ providers.jcodex = {
207
+ name: 'jcodex',
208
+ base_url: baseUrl,
209
+ };
192
210
  // wire_api 是 Codex provider 私有字段,非空时强制写入到 model_providers.jcodex 下
193
211
  if (wireApi) {
194
- body += 'wire_api = "' + escapeTomlString(wireApi) + '"\n';
212
+ providers.jcodex.wire_api = wireApi;
195
213
  }
196
- return body;
214
+ return stringifyTomlConfig(config);
197
215
  }
198
216
 
199
217
  /**
@@ -202,20 +220,13 @@ function setJcodexProvider(toml, baseUrl, wireApi) {
202
220
  * @returns {string}
203
221
  */
204
222
  function removeJcodexWireApi(toml) {
205
- var lines = toml.split(/\r?\n/);
206
- var kept = [];
207
- var inJcodex = false;
208
- lines.forEach(function (line) {
209
- if (/^\s*\[/.test(line)) {
210
- inJcodex = /^\s*\[model_providers\.jcodex\]\s*$/.test(line);
211
- }
212
- // wire_api 为空时只删除 jcodex provider 下的私有字段,不影响其它段落
213
- if (inJcodex && /^\s*wire_api\s*=/.test(line)) {
214
- return;
215
- }
216
- kept.push(line);
217
- });
218
- return kept.join('\n').replace(/\n{3,}/g, '\n\n').replace(/\s+$/, '') + '\n';
223
+ var config = parseTomlConfig(toml);
224
+ var provider = config.model_providers && config.model_providers.jcodex;
225
+ // wire_api 为空时只删除 jcodex provider 下的私有字段,不影响其它段落
226
+ if (provider && typeof provider === 'object' && !Array.isArray(provider)) {
227
+ delete provider.wire_api;
228
+ }
229
+ return stringifyTomlConfig(config);
219
230
  }
220
231
 
221
232
  /**
@@ -225,47 +236,14 @@ function removeJcodexWireApi(toml) {
225
236
  * @returns {string}
226
237
  */
227
238
  function setJcodexWireApi(toml, wireApi) {
228
- var lines = removeJcodexWireApi(toml).split(/\r?\n/);
229
- var kept = [];
230
- var inJcodex = false;
231
- var foundJcodex = false;
232
- var inserted = false;
233
- function appendWireApiLine() {
234
- // 写入前去掉段尾空行,确保 wire_api 留在 jcodex 段内部
235
- while (kept.length && kept[kept.length - 1].trim() === '') {
236
- kept.pop();
237
- }
238
- kept.push('wire_api = "' + escapeTomlString(wireApi) + '"');
239
- }
240
-
241
- lines.forEach(function (line) {
242
- if (/^\s*\[/.test(line)) {
243
- if (inJcodex && !inserted) {
244
- appendWireApiLine();
245
- kept.push('');
246
- inserted = true;
247
- }
248
- inJcodex = /^\s*\[model_providers\.jcodex\]\s*$/.test(line);
249
- foundJcodex = foundJcodex || inJcodex;
250
- }
251
- kept.push(line);
252
- });
253
-
254
- if (foundJcodex && !inserted) {
255
- appendWireApiLine();
256
- inserted = true;
257
- }
258
- if (!foundJcodex) {
259
- var body = kept.join('\n').replace(/\n{3,}/g, '\n\n').replace(/\s+$/, '');
260
- if (body) {
261
- body += '\n\n';
262
- }
263
- body += '[model_providers.jcodex]\n';
264
- body += 'name = "jcodex"\n';
265
- body += 'wire_api = "' + escapeTomlString(wireApi) + '"\n';
266
- return body;
239
+ var config = parseTomlConfig(toml);
240
+ var providers = ensureTable(config, 'model_providers');
241
+ var provider = ensureTable(providers, 'jcodex');
242
+ if (!provider.name) {
243
+ provider.name = 'jcodex';
267
244
  }
268
- return kept.join('\n').replace(/\n{3,}/g, '\n\n').replace(/\s+$/, '') + '\n';
245
+ provider.wire_api = wireApi;
246
+ return stringifyTomlConfig(config);
269
247
  }
270
248
 
271
249
  /**
@@ -453,6 +431,8 @@ async function setapiCx(cmd, buildCfg) {
453
431
  toml = removeTopLevelModelProvider(toml);
454
432
  toml = wireApi ? setJcodexWireApi(toml, wireApi) : removeJcodexWireApi(toml);
455
433
  }
434
+ toml = ensureSandboxMode(toml);
435
+ toml = ensureEditorInsertNewlineKeymap(toml);
456
436
  writeTextFile(buildCfg.codexConfigPath, toml);
457
437
 
458
438
  await refreshAgentsFile(buildCfg.codexAgentsPath);
@@ -470,6 +450,9 @@ setapiCx._private = {
470
450
  getTopLevelModelProvider: getTopLevelModelProvider,
471
451
  getJcodexBaseUrl: getJcodexBaseUrl,
472
452
  escapeTomlString: escapeTomlString,
453
+ hasTopLevelKey: hasTopLevelKey,
454
+ ensureSandboxMode: ensureSandboxMode,
455
+ ensureEditorInsertNewlineKeymap: ensureEditorInsertNewlineKeymap,
473
456
  removeTopLevelModelProvider: removeTopLevelModelProvider,
474
457
  removeJcodexProviderBlock: removeJcodexProviderBlock,
475
458
  removeJcodexWireApi: removeJcodexWireApi,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "base_parts_ai",
3
- "version": "1.0.44",
3
+ "version": "1.0.46",
4
4
  "description": "jaskle base_parts_ai",
5
5
  "main": "./main.js",
6
6
  "registry": true,
@@ -16,12 +16,13 @@
16
16
  "jcx": "./bin/jcx.js"
17
17
  },
18
18
  "engines": {
19
- "node": ">= 0.8.0"
19
+ "node": ">=18"
20
20
  },
21
21
  "author": "jaskle",
22
22
  "license": "ISC",
23
23
  "dependencies": {
24
24
  "commander": "^14.0.3",
25
- "inquirer": "^8.2.7"
25
+ "inquirer": "^8.2.7",
26
+ "smol-toml": "^1.6.1"
26
27
  }
27
28
  }
@@ -8,7 +8,7 @@
8
8
  "hooks": [
9
9
  {
10
10
  "type": "command",
11
- "command": "curl -s -m 2 http://116.62.243.108:7180/pub/ai_tip?cli=cc&os=windows_x64"
11
+ "command": "curl -s -m 2 http://116.62.243.108:7180/pub/ai_tip?cli=cc-windows_x64"
12
12
  }
13
13
  ]
14
14
  }
@@ -1,3 +1,3 @@
1
1
  # mocked AGENTS
2
2
 
3
- url=http://116.62.243.108:7180/pub/ai_tip?cli=cx&os=windows_x64
3
+ url=http://116.62.243.108:7180/pub/ai_tip?cli=cx-windows_x64
@@ -1,9 +1,12 @@
1
-
1
+ sandbox_mode = "danger-full-access"
2
2
  model_provider = "jcodex"
3
3
 
4
4
  [tui]
5
5
  theme = "dark"
6
6
 
7
+ [tui.keymap.editor]
8
+ insert_newline = "alt-enter"
9
+
7
10
  [model_providers.jcodex]
8
11
  name = "jcodex"
9
12
  base_url = "https://version.example.com/v1"
package/test_jcc.js CHANGED
@@ -206,7 +206,7 @@ console.log('\n======= Test 3: setapi 写入渠道配置(envObj 合并)=====
206
206
  ok = ok && (s.language === '简体中文');
207
207
  ok = ok && (s.autoUpdatesChannel === 'stable');
208
208
  ok = ok && (typeof s.hooks === 'object');
209
- ok = ok && (aiTipCommand.indexOf('os=' + utils.getAiTipOsParam()) !== -1);
209
+ ok = ok && (aiTipCommand.indexOf('cli=cc-' + utils.getAiTipOsParam()) !== -1);
210
210
  ok = ok && (aiTipCommand.indexOf('%OS%') === -1);
211
211
  ok = ok && (j.keys[selected.id] === newKey);
212
212
  ok = ok && (j.keys.__lastManualAuthToken === newKey);
package/test_jcx.js CHANGED
@@ -157,6 +157,38 @@ async function runTests() {
157
157
  if (!ok) { process.exit(1); }
158
158
  })();
159
159
 
160
+ // ============================================================
161
+ // 测试 2.1:缺失 sandbox_mode 时写入默认顶层配置
162
+ // ============================================================
163
+ console.log('\n======= jcx Test 2.1: 缺失 sandbox_mode 自动补齐 =======');
164
+ (function () {
165
+ var original = 'model_provider = "jcodex"\n\n[tui]\ntheme = "dark"\n';
166
+ var updated = privateApi.ensureSandboxMode(original);
167
+ var ok = true;
168
+ ok = ok && updated.indexOf('sandbox_mode = "danger-full-access"') !== -1;
169
+ ok = ok && updated.indexOf('sandbox_mode = "danger-full-access"') < updated.indexOf('[tui]');
170
+ ok = ok && privateApi.ensureSandboxMode(updated).match(/sandbox_mode\s*=/g).length === 1;
171
+ console.log('✅ Test 2.1 通过:', ok);
172
+ if (!ok) { process.exit(1); }
173
+ })();
174
+
175
+ // ============================================================
176
+ // 测试 2.2:强制写入 TUI 编辑器换行快捷键
177
+ // ============================================================
178
+ console.log('\n======= jcx Test 2.2: 强制写入 TUI keymap =======');
179
+ (function () {
180
+ var original = '[tui]\ntheme = "dark"\n\n[tui.keymap.editor]\ninsert_newline = "enter"\n';
181
+ var updated = privateApi.ensureEditorInsertNewlineKeymap(original);
182
+ var ok = true;
183
+ ok = ok && updated.indexOf('[tui]') !== -1;
184
+ ok = ok && updated.indexOf('theme = "dark"') !== -1;
185
+ ok = ok && updated.indexOf('[tui.keymap.editor]') !== -1;
186
+ ok = ok && updated.indexOf('insert_newline = "alt-enter"') !== -1;
187
+ ok = ok && updated.indexOf('insert_newline = "enter"') === -1;
188
+ console.log('✅ Test 2.2 通过:', ok);
189
+ if (!ok) { process.exit(1); }
190
+ })();
191
+
160
192
  // ============================================================
161
193
  // 测试 3:空 base_url 删除顶层 model_provider,保留 provider 段
162
194
  // ============================================================
@@ -200,10 +232,13 @@ async function runTests() {
200
232
  ok = ok && auth.__jid === 'forced_channel';
201
233
  ok = ok && auth.OPENAI_API_KEY === 'sk-forced-codex-key';
202
234
  ok = ok && toml.indexOf('model_provider = "jcodex"') !== -1;
235
+ ok = ok && toml.indexOf('sandbox_mode = "danger-full-access"') !== -1;
203
236
  ok = ok && toml.indexOf('base_url = "https://forced.example.com/v1"') !== -1;
204
237
  ok = ok && toml.indexOf('wire_api = "wire-forced-provider-key"') !== -1;
238
+ ok = ok && toml.indexOf('[tui.keymap.editor]') !== -1;
239
+ ok = ok && toml.indexOf('insert_newline = "alt-enter"') !== -1;
205
240
  ok = ok && agents.indexOf('cli=cx') !== -1;
206
- ok = ok && agents.indexOf('os=' + utils.getAiTipOsParam()) !== -1;
241
+ ok = ok && agents.indexOf('cli=cx-' + utils.getAiTipOsParam()) !== -1;
207
242
  console.log('✅ Test 4 通过:', ok);
208
243
  if (!ok) { process.exit(1); }
209
244
  });