duojie-helper 0.2.21 → 0.2.22
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 +160 -160
- package/bin/cli.js +328 -328
- package/package.json +53 -53
- package/src/index.js +323 -323
- package/src/tools/claude.js +198 -198
- package/src/tools/cline.js +103 -103
- package/src/tools/codex.js +154 -154
- package/src/tools/continue.js +158 -158
- package/src/tools/cursor.js +99 -99
- package/src/tools/droid.js +262 -261
- package/src/tools/openclaw.js +292 -292
- package/src/tools/opencode.js +216 -216
package/src/tools/droid.js
CHANGED
|
@@ -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 '
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
*
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
...(models.
|
|
115
|
-
...(models.
|
|
116
|
-
...(models.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
(m.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
...
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
(m.
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
(m.
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
|
|
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;
|