duojie-helper 0.2.21 → 0.2.23

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.
@@ -1,261 +1,262 @@
1
- import fs from 'fs-extra';
2
- import path from 'path';
3
- import os from 'os';
4
- import { API_CONFIG, getModels, getApiBaseForProtocol } from '../index.js';
5
-
6
- function normalizeBaseUrl(url) {
7
- return `${url || ''}`.replace(/\/+$/, '');
8
- }
9
-
10
- /**
11
- * 获取 Droid (Factory) 配置路径
12
- */
13
- function getDroidConfigPaths() {
14
- const home = os.homedir();
15
- return {
16
- configDir: path.join(home, '.factory'),
17
- configFile: path.join(home, '.factory', 'config.json'),
18
- settingsFile: path.join(home, '.factory', 'settings.json'),
19
- };
20
- }
21
-
22
- /**
23
- * 生成友好的模型显示名称
24
- * @param {string} modelId - 模型 ID
25
- * @returns {string} 显示名称
26
- */
27
- function generateDisplayName(modelId) {
28
- // 将 model id 转换为友好名称
29
- // claude-opus-4-6-kiro -> Opus 4.6 Kiro
30
- // gpt-5.2-codex -> GPT 5.2 Codex
31
- // gemini-3-pro-preview -> Gemini 3 Pro Preview
32
-
33
- let name = modelId;
34
-
35
- // 移除常见前缀
36
- if (name.startsWith('claude-')) {
37
- name = name.substring(7);
38
- } else if (name.startsWith('gpt-')) {
39
- name = 'GPT ' + name.substring(4);
40
- } else if (name.startsWith('gemini-')) {
41
- name = 'Gemini ' + name.substring(7);
42
- } else if (name.startsWith('glm-')) {
43
- name = 'GLM-' + name.substring(4);
44
- }
45
-
46
- // 将连字符转换为空格,并首字母大写
47
- name = name
48
- .split('-')
49
- .map(part => {
50
- // 处理版本号(如 4-5 -> 4.5)
51
- if (/^\d+$/.test(part)) {
52
- return part;
53
- }
54
- // 首字母大写
55
- return part.charAt(0).toUpperCase() + part.slice(1);
56
- })
57
- .join(' ');
58
-
59
- // 处理版本号格式(4 5 -> 4.5)
60
- name = name.replace(/(\d+)\s+(\d+)(?=\s|$)/g, '$1.$2');
61
-
62
- // 添加后缀标识
63
- return `${name} [duojie.games]`;
64
- }
65
-
66
- /**
67
- * 根据模型 ID 确定 provider
68
- * @param {string} modelId - 模型 ID
69
- * @param {string[]} endpoints - 支持的 endpoint 类型
70
- * @returns {string} provider 名称
71
- */
72
- function determineProvider(modelId, endpoints = []) {
73
- if (modelId.startsWith('gpt')) return 'openai';
74
- return 'anthropic';
75
- }
76
-
77
- /**
78
- * 配置 Droid (Factory)
79
- * 自动配置所有可用模型到 custom_models
80
- */
81
- export async function configureDroid(apiKey) {
82
- const paths = getDroidConfigPaths();
83
-
84
- try {
85
- // 确保目录存在
86
- await fs.ensureDir(paths.configDir);
87
-
88
- // 1. 读取现有配置(如果存在)
89
- let existingConfig = {};
90
- if (await fs.pathExists(paths.configFile)) {
91
- try {
92
- existingConfig = await fs.readJson(paths.configFile);
93
- } catch {
94
- existingConfig = {};
95
- }
96
- }
97
-
98
- // 2. 获取模型列表
99
- const models = getModels();
100
- const rawModels = API_CONFIG.rawModels || [];
101
-
102
- // 创建模型 ID 到 endpoints 的映射
103
- const endpointsMap = {};
104
- for (const m of rawModels) {
105
- endpointsMap[m.id] = m.supported_endpoint_types || [];
106
- }
107
-
108
- // 3. 构建 Duojie 的 custom_models
109
- const duojieModels = [];
110
-
111
- // 处理所有模型类别
112
- const allModels = [
113
- ...(models.claude || []),
114
- ...(models.gpt || []),
115
- ...(models.gemini || []),
116
- ...(models.other || []),
117
- ];
118
-
119
- // 默认模型优先放在列表前(便于用户在 UI 中选择)
120
- const preferredId = 'gpt-5.2';
121
- const preferred = allModels.find(m => m.id === preferredId) || null;
122
- const orderedModels = preferred
123
- ? [preferred, ...allModels.filter(m => m.id !== preferredId)]
124
- : allModels;
125
-
126
- for (const m of orderedModels) {
127
- const endpoints = endpointsMap[m.id] || [];
128
- const provider = determineProvider(m.id, endpoints);
129
-
130
- duojieModels.push({
131
- model_display_name: generateDisplayName(m.id),
132
- model: m.id,
133
- base_url: normalizeBaseUrl(getApiBaseForProtocol(provider)),
134
- api_key: apiKey,
135
- provider: provider,
136
- supports_vision: true,
137
- max_tokens: 16384,
138
- });
139
- }
140
-
141
- if (duojieModels.length === 0) {
142
- return {
143
- success: false,
144
- message: '没有可用的模型',
145
- };
146
- }
147
-
148
- // 4. 合并配置(保留用户其他自定义模型)
149
- let existingCustomModels = existingConfig.custom_models || [];
150
-
151
- // 过滤掉已有的 Duojie 模型(通过 base_url 或 display_name 判断)
152
- existingCustomModels = existingCustomModels.filter(m => {
153
- const isDuojie =
154
- (m.base_url && m.base_url.includes('duojie')) ||
155
- (m.model_display_name && m.model_display_name.includes('[duojie.games]'));
156
- return !isDuojie;
157
- });
158
-
159
- // 5. 合并新旧模型
160
- const newConfig = {
161
- ...existingConfig,
162
- custom_models: [
163
- ...existingCustomModels,
164
- ...duojieModels,
165
- ],
166
- };
167
-
168
- // 6. 写入配置文件
169
- await fs.writeJson(paths.configFile, newConfig, { spaces: 2 });
170
-
171
- // 7. 清空 settings.json 中的 customModels(如文件和字段存在)
172
- if (await fs.pathExists(paths.settingsFile)) {
173
- try {
174
- const settings = await fs.readJson(paths.settingsFile);
175
- if (Array.isArray(settings.customModels) && settings.customModels.length > 0) {
176
- settings.customModels = [];
177
- await fs.writeJson(paths.settingsFile, settings, { spaces: 2 });
178
- }
179
- } catch {
180
- // 忽略,不影响主流程
181
- }
182
- }
183
-
184
- return {
185
- success: true,
186
- message: `已配置 ${duojieModels.length} 个模型 → ${paths.configFile}`,
187
- configPath: paths.configFile,
188
- hint: '重启 Factory 使配置生效',
189
- };
190
- } catch (error) {
191
- return {
192
- success: false,
193
- message: `配置失败: ${error.message}`,
194
- };
195
- }
196
- }
197
-
198
- /**
199
- * 检查 Droid 配置状态
200
- */
201
- configureDroid.checkStatus = async function() {
202
- const paths = getDroidConfigPaths();
203
-
204
- if (await fs.pathExists(paths.configFile)) {
205
- try {
206
- const config = await fs.readJson(paths.configFile);
207
-
208
- // 检查是否有 Duojie 的自定义模型
209
- const customModels = config.custom_models || [];
210
- const duojieModels = customModels.filter(m =>
211
- (m.base_url && m.base_url.includes('duojie')) ||
212
- (m.model_display_name && m.model_display_name.includes('[duojie.games]'))
213
- );
214
-
215
- if (duojieModels.length > 0) {
216
- return {
217
- configured: true,
218
- message: `已配置 ${duojieModels.length} 个 Duojie 模型`,
219
- };
220
- } else if (customModels.length > 0) {
221
- return {
222
- configured: true,
223
- message: '已配置(非 Duojie)',
224
- };
225
- }
226
- } catch {
227
- // ignore
228
- }
229
- }
230
-
231
- return { configured: false };
232
- };
233
-
234
- /**
235
- * 撤销 Droid 配置
236
- */
237
- configureDroid.revoke = async function() {
238
- const paths = getDroidConfigPaths();
239
-
240
- if (await fs.pathExists(paths.configFile)) {
241
- try {
242
- const config = await fs.readJson(paths.configFile);
243
-
244
- // 只删除 Duojie 的自定义模型
245
- if (config.custom_models) {
246
- config.custom_models = config.custom_models.filter(m => {
247
- const isDuojie =
248
- (m.base_url && m.base_url.includes('duojie')) ||
249
- (m.model_display_name && m.model_display_name.includes('[duojie.games]'));
250
- return !isDuojie;
251
- });
252
- }
253
-
254
- await fs.writeJson(paths.configFile, config, { spaces: 2 });
255
- } catch {
256
- // ignore
257
- }
258
- }
259
- };
260
-
261
- export default configureDroid;
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import { API_CONFIG, getModels, getApiBaseForProtocol } from '../index.js';
5
+
6
+ function normalizeBaseUrl(url) {
7
+ return `${url || ''}`.replace(/\/+$/, '');
8
+ }
9
+
10
+ /**
11
+ * 获取 Droid (Factory) 配置路径
12
+ */
13
+ function getDroidConfigPaths() {
14
+ const home = os.homedir();
15
+ return {
16
+ configDir: path.join(home, '.factory'),
17
+ configFile: path.join(home, '.factory', 'config.json'),
18
+ settingsFile: path.join(home, '.factory', 'settings.json'),
19
+ };
20
+ }
21
+
22
+ /**
23
+ * 生成友好的模型显示名称
24
+ * @param {string} modelId - 模型 ID
25
+ * @returns {string} 显示名称
26
+ */
27
+ function generateDisplayName(modelId) {
28
+ // 将 model id 转换为友好名称
29
+ // claude-opus-4-6-kiro -> Opus 4.6 Kiro
30
+ // gpt-5.2-codex -> GPT 5.2 Codex
31
+ // gemini-3-pro-preview -> Gemini 3 Pro Preview
32
+
33
+ let name = modelId;
34
+
35
+ // 移除常见前缀
36
+ if (name.startsWith('claude-')) {
37
+ name = name.substring(7);
38
+ } else if (name.startsWith('gpt-')) {
39
+ name = 'GPT ' + name.substring(4);
40
+ } else if (name.startsWith('gemini-')) {
41
+ name = 'Gemini ' + name.substring(7);
42
+ } else if (name.startsWith('glm-')) {
43
+ name = 'GLM-' + name.substring(4);
44
+ }
45
+
46
+ // 将连字符转换为空格,并首字母大写
47
+ name = name
48
+ .split('-')
49
+ .map(part => {
50
+ // 处理版本号(如 4-5 -> 4.5)
51
+ if (/^\d+$/.test(part)) {
52
+ return part;
53
+ }
54
+ // 首字母大写
55
+ return part.charAt(0).toUpperCase() + part.slice(1);
56
+ })
57
+ .join(' ');
58
+
59
+ // 处理版本号格式(4 5 -> 4.5)
60
+ name = name.replace(/(\d+)\s+(\d+)(?=\s|$)/g, '$1.$2');
61
+
62
+ // 添加后缀标识
63
+ return `${name} [duojie.games]`;
64
+ }
65
+
66
+ /**
67
+ * 根据模型 ID 确定 provider
68
+ * @param {string} modelId - 模型 ID
69
+ * @param {string[]} endpoints - 支持的 endpoint 类型
70
+ * @returns {string} provider 名称
71
+ */
72
+ function determineProvider(modelId, endpoints = []) {
73
+ if (modelId.startsWith('gpt')) return 'openai';
74
+ if (modelId.startsWith('glm')) return 'generic-chat-completion-api';
75
+ return 'anthropic';
76
+ }
77
+
78
+ /**
79
+ * 配置 Droid (Factory)
80
+ * 自动配置所有可用模型到 custom_models
81
+ */
82
+ export async function configureDroid(apiKey) {
83
+ const paths = getDroidConfigPaths();
84
+
85
+ try {
86
+ // 确保目录存在
87
+ await fs.ensureDir(paths.configDir);
88
+
89
+ // 1. 读取现有配置(如果存在)
90
+ let existingConfig = {};
91
+ if (await fs.pathExists(paths.configFile)) {
92
+ try {
93
+ existingConfig = await fs.readJson(paths.configFile);
94
+ } catch {
95
+ existingConfig = {};
96
+ }
97
+ }
98
+
99
+ // 2. 获取模型列表
100
+ const models = getModels();
101
+ const rawModels = API_CONFIG.rawModels || [];
102
+
103
+ // 创建模型 ID 到 endpoints 的映射
104
+ const endpointsMap = {};
105
+ for (const m of rawModels) {
106
+ endpointsMap[m.id] = m.supported_endpoint_types || [];
107
+ }
108
+
109
+ // 3. 构建 Duojie 的 custom_models
110
+ const duojieModels = [];
111
+
112
+ // 处理所有模型类别
113
+ const allModels = [
114
+ ...(models.claude || []),
115
+ ...(models.gpt || []),
116
+ ...(models.gemini || []),
117
+ ...(models.other || []),
118
+ ];
119
+
120
+ // 默认模型优先放在列表前(便于用户在 UI 中选择)
121
+ const preferredId = 'claude-opus-4-6-gemini';
122
+ const preferred = allModels.find(m => m.id === preferredId) || null;
123
+ const orderedModels = preferred
124
+ ? [preferred, ...allModels.filter(m => m.id !== preferredId)]
125
+ : allModels;
126
+
127
+ for (const m of orderedModels) {
128
+ const endpoints = endpointsMap[m.id] || [];
129
+ const provider = determineProvider(m.id, endpoints);
130
+
131
+ duojieModels.push({
132
+ model_display_name: generateDisplayName(m.id),
133
+ model: m.id,
134
+ base_url: normalizeBaseUrl(getApiBaseForProtocol(provider)),
135
+ api_key: apiKey,
136
+ provider: provider,
137
+ supports_vision: true,
138
+ max_tokens: 16384,
139
+ });
140
+ }
141
+
142
+ if (duojieModels.length === 0) {
143
+ return {
144
+ success: false,
145
+ message: '没有可用的模型',
146
+ };
147
+ }
148
+
149
+ // 4. 合并配置(保留用户其他自定义模型)
150
+ let existingCustomModels = existingConfig.custom_models || [];
151
+
152
+ // 过滤掉已有的 Duojie 模型(通过 base_url 或 display_name 判断)
153
+ existingCustomModels = existingCustomModels.filter(m => {
154
+ const isDuojie =
155
+ (m.base_url && m.base_url.includes('duojie')) ||
156
+ (m.model_display_name && m.model_display_name.includes('[duojie.games]'));
157
+ return !isDuojie;
158
+ });
159
+
160
+ // 5. 合并新旧模型
161
+ const newConfig = {
162
+ ...existingConfig,
163
+ custom_models: [
164
+ ...existingCustomModels,
165
+ ...duojieModels,
166
+ ],
167
+ };
168
+
169
+ // 6. 写入配置文件
170
+ await fs.writeJson(paths.configFile, newConfig, { spaces: 2 });
171
+
172
+ // 7. 清空 settings.json 中的 customModels(如文件和字段存在)
173
+ if (await fs.pathExists(paths.settingsFile)) {
174
+ try {
175
+ const settings = await fs.readJson(paths.settingsFile);
176
+ if (Array.isArray(settings.customModels) && settings.customModels.length > 0) {
177
+ settings.customModels = [];
178
+ await fs.writeJson(paths.settingsFile, settings, { spaces: 2 });
179
+ }
180
+ } catch {
181
+ // 忽略,不影响主流程
182
+ }
183
+ }
184
+
185
+ return {
186
+ success: true,
187
+ message: `已配置 ${duojieModels.length} 个模型 → ${paths.configFile}`,
188
+ configPath: paths.configFile,
189
+ hint: '重启 Factory 使配置生效',
190
+ };
191
+ } catch (error) {
192
+ return {
193
+ success: false,
194
+ message: `配置失败: ${error.message}`,
195
+ };
196
+ }
197
+ }
198
+
199
+ /**
200
+ * 检查 Droid 配置状态
201
+ */
202
+ configureDroid.checkStatus = async function() {
203
+ const paths = getDroidConfigPaths();
204
+
205
+ if (await fs.pathExists(paths.configFile)) {
206
+ try {
207
+ const config = await fs.readJson(paths.configFile);
208
+
209
+ // 检查是否有 Duojie 的自定义模型
210
+ const customModels = config.custom_models || [];
211
+ const duojieModels = customModels.filter(m =>
212
+ (m.base_url && m.base_url.includes('duojie')) ||
213
+ (m.model_display_name && m.model_display_name.includes('[duojie.games]'))
214
+ );
215
+
216
+ if (duojieModels.length > 0) {
217
+ return {
218
+ configured: true,
219
+ message: `已配置 ${duojieModels.length} 个 Duojie 模型`,
220
+ };
221
+ } else if (customModels.length > 0) {
222
+ return {
223
+ configured: true,
224
+ message: '已配置(非 Duojie)',
225
+ };
226
+ }
227
+ } catch {
228
+ // ignore
229
+ }
230
+ }
231
+
232
+ return { configured: false };
233
+ };
234
+
235
+ /**
236
+ * 撤销 Droid 配置
237
+ */
238
+ configureDroid.revoke = async function() {
239
+ const paths = getDroidConfigPaths();
240
+
241
+ if (await fs.pathExists(paths.configFile)) {
242
+ try {
243
+ const config = await fs.readJson(paths.configFile);
244
+
245
+ // 只删除 Duojie 的自定义模型
246
+ if (config.custom_models) {
247
+ config.custom_models = config.custom_models.filter(m => {
248
+ const isDuojie =
249
+ (m.base_url && m.base_url.includes('duojie')) ||
250
+ (m.model_display_name && m.model_display_name.includes('[duojie.games]'));
251
+ return !isDuojie;
252
+ });
253
+ }
254
+
255
+ await fs.writeJson(paths.configFile, config, { spaces: 2 });
256
+ } catch {
257
+ // ignore
258
+ }
259
+ }
260
+ };
261
+
262
+ export default configureDroid;