duojie-helper 0.2.4 → 0.2.7
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/package.json +1 -1
- package/src/tools/claude.js +11 -15
- package/src/tools/cline.js +1 -1
- package/src/tools/codex.js +1 -1
- package/src/tools/cursor.js +1 -1
- package/src/tools/droid.js +1 -1
- package/src/tools/openclaw.js +92 -198
package/package.json
CHANGED
package/src/tools/claude.js
CHANGED
|
@@ -50,23 +50,19 @@ export async function configureClaudeCode(apiKey) {
|
|
|
50
50
|
const models = getModels();
|
|
51
51
|
const claudeModels = models.claude || [];
|
|
52
52
|
|
|
53
|
-
//
|
|
53
|
+
// 查找各类型模型(优先查找最新 kiro 版本)
|
|
54
54
|
let sonnetModel = null;
|
|
55
55
|
let opusModel = null;
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (id.includes('sonnet') && !sonnetModel) {
|
|
67
|
-
sonnetModel = m.id;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
57
|
+
// 优先级:opus-4-6-kiro > opus-4-6 > opus+kiro > opus > 第一个opus
|
|
58
|
+
const opusKiro46 = claudeModels.find(m => m.id.includes('opus-4-6') && m.id.includes('kiro'));
|
|
59
|
+
const opus46 = claudeModels.find(m => m.id.includes('opus-4-6'));
|
|
60
|
+
const opusKiro = claudeModels.find(m => m.id.includes('opus') && m.id.includes('kiro'));
|
|
61
|
+
const opusAny = claudeModels.find(m => m.id.includes('opus'));
|
|
62
|
+
opusModel = opusKiro46?.id || opus46?.id || opusKiro?.id || opusAny?.id || null;
|
|
63
|
+
|
|
64
|
+
// sonnet 模型
|
|
65
|
+
sonnetModel = claudeModels.find(m => m.id.includes('sonnet'))?.id || null;
|
|
70
66
|
|
|
71
67
|
// 4. 设置 Duojie API 环境变量
|
|
72
68
|
settings.env.ANTHROPIC_API_KEY = apiKey;
|
|
@@ -82,7 +78,7 @@ export async function configureClaudeCode(apiKey) {
|
|
|
82
78
|
settings.env.ANTHROPIC_DEFAULT_OPUS_MODEL = opusModel;
|
|
83
79
|
}
|
|
84
80
|
|
|
85
|
-
// 设置默认主模型为 claude-opus-4-
|
|
81
|
+
// 设置默认主模型为 claude-opus-4-6-kiro(或可用的 opus)
|
|
86
82
|
const defaultModel = opusModel || sonnetModel || claudeModels[0]?.id;
|
|
87
83
|
if (defaultModel) {
|
|
88
84
|
settings.model = defaultModel;
|
package/src/tools/cline.js
CHANGED
|
@@ -48,7 +48,7 @@ ${chalk.cyan('步骤 4:')} 填入以下配置:
|
|
|
48
48
|
${chalk.green('Base URL:')} ${baseUrl}
|
|
49
49
|
${chalk.green('API Key:')} ${apiKey}
|
|
50
50
|
${chalk.green('Model:')} 选择 "Enter model ID" 并输入模型名称
|
|
51
|
-
例如: claude-
|
|
51
|
+
例如: claude-opus-4-6-kiro 或 gpt-4o
|
|
52
52
|
|
|
53
53
|
${chalk.cyan('步骤 5:')} 可选配置:
|
|
54
54
|
• ${chalk.green('Max Tokens:')} 8192 (推荐)
|
package/src/tools/codex.js
CHANGED
|
@@ -144,7 +144,7 @@ export async function configureCodex(apiKey) {
|
|
|
144
144
|
// 如果没有设置默认 provider,设置为 duojie
|
|
145
145
|
if (!existingConfig.model_provider) {
|
|
146
146
|
existingConfig.model_provider = 'duojie';
|
|
147
|
-
existingConfig.model = '
|
|
147
|
+
existingConfig.model = 'claude-opus-4-6-kiro';
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
// 生成 TOML 内容
|
package/src/tools/cursor.js
CHANGED
|
@@ -47,7 +47,7 @@ ${chalk.cyan('步骤 3:')} 填入以下配置:
|
|
|
47
47
|
${chalk.green('OpenAI API Key:')} ${apiKey}
|
|
48
48
|
${chalk.green('Override OpenAI Base URL:')} ${baseUrl}
|
|
49
49
|
${chalk.green('Model Name:')} 输入模型名称(注意大小写)
|
|
50
|
-
例如: claude-
|
|
50
|
+
例如: claude-opus-4-6-kiro
|
|
51
51
|
或: gpt-4o
|
|
52
52
|
|
|
53
53
|
${chalk.cyan('步骤 4:')} 点击保存,然后在模型列表中选择刚添加的模型
|
package/src/tools/droid.js
CHANGED
|
@@ -21,7 +21,7 @@ function getDroidConfigPaths() {
|
|
|
21
21
|
*/
|
|
22
22
|
function generateDisplayName(modelId) {
|
|
23
23
|
// 将 model id 转换为友好名称
|
|
24
|
-
// claude-opus-4-
|
|
24
|
+
// claude-opus-4-6-kiro -> Opus 4.6 Kiro
|
|
25
25
|
// gpt-5.2-codex -> GPT 5.2 Codex
|
|
26
26
|
// gemini-3-pro-preview -> Gemini 3 Pro Preview
|
|
27
27
|
|
package/src/tools/openclaw.js
CHANGED
|
@@ -5,199 +5,112 @@ import { API_CONFIG, getModels } from '../index.js';
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* 获取 OpenClaw 配置路径
|
|
8
|
+
* 支持 macOS / Linux / Windows
|
|
8
9
|
*/
|
|
9
10
|
function getOpenClawConfigPaths() {
|
|
10
11
|
const home = os.homedir();
|
|
11
12
|
return {
|
|
12
13
|
configDir: path.join(home, '.openclaw'),
|
|
13
14
|
configFile: path.join(home, '.openclaw', 'openclaw.json'),
|
|
14
|
-
agentsDir: path.join(home, '.openclaw', 'agents'),
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* 读取并解析 openclaw.json(支持 JSON5 注释/尾逗号)
|
|
20
|
+
*/
|
|
21
|
+
async function readConfig(filePath) {
|
|
22
|
+
try {
|
|
23
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
24
|
+
const clean = content
|
|
25
|
+
.replace(/\/\/.*$/gm, '')
|
|
26
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
27
|
+
.replace(/,(\s*[}\]])/g, '$1');
|
|
28
|
+
return JSON.parse(clean);
|
|
29
|
+
} catch {
|
|
30
|
+
return {};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 从模型列表中选出最优默认模型 ID
|
|
36
|
+
* 优先级:opus-4-6-kiro > opus-4-6 > opus+kiro > opus > sonnet > 第一个
|
|
37
|
+
*/
|
|
38
|
+
function pickDefaultModel(models) {
|
|
39
|
+
const all = [
|
|
40
|
+
...(models.claude || []),
|
|
41
|
+
...(models.gpt || []),
|
|
42
|
+
...(models.gemini || []),
|
|
43
|
+
...(models.other || []),
|
|
44
|
+
];
|
|
45
|
+
if (all.length === 0) return 'claude-opus-4-6-kiro';
|
|
46
|
+
|
|
47
|
+
const claudeList = models.claude || [];
|
|
48
|
+
if (claudeList.length > 0) {
|
|
49
|
+
return (
|
|
50
|
+
claudeList.find(m => m.id.includes('opus-4-6') && m.id.includes('kiro'))?.id ||
|
|
51
|
+
claudeList.find(m => m.id.includes('opus-4-6'))?.id ||
|
|
52
|
+
claudeList.find(m => m.id.includes('opus') && m.id.includes('kiro'))?.id ||
|
|
53
|
+
claudeList.find(m => m.id.includes('opus'))?.id ||
|
|
54
|
+
claudeList.find(m => m.id.includes('sonnet'))?.id ||
|
|
55
|
+
claudeList[0].id
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return all[0].id;
|
|
59
|
+
}
|
|
60
|
+
|
|
18
61
|
/**
|
|
19
62
|
* 配置 OpenClaw
|
|
20
|
-
*
|
|
63
|
+
*
|
|
64
|
+
* 使用 OpenClaw 标准的 env + agents.defaults.model 格式:
|
|
65
|
+
* env.DUOJIE_API_KEY → API 密钥
|
|
66
|
+
* env.DUOJIE_BASE_URL → 自定义 base URL
|
|
67
|
+
* agents.defaults.model.primary → "duojie/<model-id>"
|
|
21
68
|
*/
|
|
22
69
|
export async function configureOpenClaw(apiKey) {
|
|
23
70
|
const paths = getOpenClawConfigPaths();
|
|
24
|
-
|
|
71
|
+
|
|
25
72
|
try {
|
|
26
|
-
// 确保目录存在
|
|
27
73
|
await fs.ensureDir(paths.configDir);
|
|
28
74
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
try {
|
|
33
|
-
const content = await fs.readFile(paths.configFile, 'utf-8');
|
|
34
|
-
// 简单的 JSON5 兼容处理(去除注释和尾逗号)
|
|
35
|
-
const cleanContent = content
|
|
36
|
-
.replace(/\/\/.*$/gm, '') // 移除单行注释
|
|
37
|
-
.replace(/\/\*[\s\S]*?\*\//g, '') // 移除多行注释
|
|
38
|
-
.replace(/,(\s*[}\]])/g, '$1'); // 移除尾逗号
|
|
39
|
-
existingConfig = JSON.parse(cleanContent);
|
|
40
|
-
} catch {
|
|
41
|
-
existingConfig = {};
|
|
42
|
-
}
|
|
43
|
-
}
|
|
75
|
+
const existingConfig = await fs.pathExists(paths.configFile)
|
|
76
|
+
? await readConfig(paths.configFile)
|
|
77
|
+
: {};
|
|
44
78
|
|
|
45
|
-
// 获取模型列表
|
|
46
79
|
const models = getModels();
|
|
80
|
+
const defaultModelId = pickDefaultModel(models);
|
|
81
|
+
const totalModels = Object.values(models).reduce((s, arr) => s + arr.length, 0);
|
|
47
82
|
|
|
48
|
-
//
|
|
49
|
-
const claudeModels = (models.claude || []).map(m => ({
|
|
50
|
-
id: m.id,
|
|
51
|
-
name: m.name,
|
|
52
|
-
reasoning: false,
|
|
53
|
-
input: ['text', 'image'],
|
|
54
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
55
|
-
contextWindow: 200000,
|
|
56
|
-
maxTokens: 64000,
|
|
57
|
-
}));
|
|
58
|
-
|
|
59
|
-
// GPT 模型 (openai-responses 或 openai-completions)
|
|
60
|
-
const gptModels = (models.gpt || []).map(m => ({
|
|
61
|
-
id: m.id,
|
|
62
|
-
name: m.name,
|
|
63
|
-
reasoning: false,
|
|
64
|
-
input: ['text', 'image'],
|
|
65
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
66
|
-
contextWindow: 128000,
|
|
67
|
-
maxTokens: 16384,
|
|
68
|
-
}));
|
|
69
|
-
|
|
70
|
-
// Gemini 模型
|
|
71
|
-
const geminiModels = (models.gemini || []).map(m => ({
|
|
72
|
-
id: m.id,
|
|
73
|
-
name: m.name,
|
|
74
|
-
reasoning: false,
|
|
75
|
-
input: ['text', 'image'],
|
|
76
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
77
|
-
contextWindow: 1000000,
|
|
78
|
-
maxTokens: 65536,
|
|
79
|
-
}));
|
|
80
|
-
|
|
81
|
-
// 其他模型
|
|
82
|
-
const otherModels = (models.other || []).map(m => ({
|
|
83
|
-
id: m.id,
|
|
84
|
-
name: m.name,
|
|
85
|
-
reasoning: false,
|
|
86
|
-
input: ['text', 'image'],
|
|
87
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
88
|
-
contextWindow: 128000,
|
|
89
|
-
maxTokens: 16384,
|
|
90
|
-
}));
|
|
91
|
-
|
|
92
|
-
// 构建 providers
|
|
93
|
-
const providers = {};
|
|
94
|
-
|
|
95
|
-
if (claudeModels.length > 0) {
|
|
96
|
-
providers['duojie-claude'] = {
|
|
97
|
-
baseUrl: API_CONFIG.baseUrl,
|
|
98
|
-
apiKey: apiKey,
|
|
99
|
-
api: 'anthropic-messages',
|
|
100
|
-
models: claudeModels,
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (gptModels.length > 0) {
|
|
105
|
-
// GPT 使用 openai-responses (如果支持) 或 openai-completions
|
|
106
|
-
const gptApi = models.gpt[0]?.api || 'openai-responses';
|
|
107
|
-
providers['duojie-gpt'] = {
|
|
108
|
-
baseUrl: API_CONFIG.baseUrl,
|
|
109
|
-
apiKey: apiKey,
|
|
110
|
-
api: gptApi,
|
|
111
|
-
models: gptModels,
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (geminiModels.length > 0) {
|
|
116
|
-
providers['duojie-gemini'] = {
|
|
117
|
-
baseUrl: API_CONFIG.baseUrl,
|
|
118
|
-
apiKey: apiKey,
|
|
119
|
-
api: 'anthropic-messages',
|
|
120
|
-
models: geminiModels,
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (otherModels.length > 0) {
|
|
125
|
-
providers['duojie-other'] = {
|
|
126
|
-
baseUrl: API_CONFIG.baseUrl,
|
|
127
|
-
apiKey: apiKey,
|
|
128
|
-
api: 'openai-completions',
|
|
129
|
-
models: otherModels,
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// 确定默认模型 - 优先使用 Claude,其次 GPT
|
|
134
|
-
let defaultModelProvider = 'duojie-claude';
|
|
135
|
-
let defaultModel = claudeModels[0]?.id || gptModels[0]?.id || 'claude-sonnet-4-5-20250929';
|
|
136
|
-
|
|
137
|
-
if (claudeModels.length > 0) {
|
|
138
|
-
defaultModelProvider = 'duojie-claude';
|
|
139
|
-
defaultModel = claudeModels[0].id;
|
|
140
|
-
} else if (gptModels.length > 0) {
|
|
141
|
-
defaultModelProvider = 'duojie-gpt';
|
|
142
|
-
defaultModel = gptModels[0].id;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// 构建模型允许列表
|
|
146
|
-
const modelAllowList = {};
|
|
147
|
-
for (const m of claudeModels) {
|
|
148
|
-
modelAllowList[`duojie-claude/${m.id}`] = { alias: m.name };
|
|
149
|
-
}
|
|
150
|
-
for (const m of gptModels) {
|
|
151
|
-
modelAllowList[`duojie-gpt/${m.id}`] = { alias: m.name };
|
|
152
|
-
}
|
|
153
|
-
for (const m of geminiModels) {
|
|
154
|
-
modelAllowList[`duojie-gemini/${m.id}`] = { alias: m.name };
|
|
155
|
-
}
|
|
156
|
-
for (const m of otherModels) {
|
|
157
|
-
modelAllowList[`duojie-other/${m.id}`] = { alias: m.name };
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// 统计配置的模型数量
|
|
161
|
-
const totalModels = claudeModels.length + gptModels.length + geminiModels.length + otherModels.length;
|
|
162
|
-
|
|
163
|
-
// 合并配置(不覆盖用户其他配置)
|
|
83
|
+
// 合并配置,保留用户已有的非 Duojie 设置
|
|
164
84
|
const newConfig = {
|
|
165
85
|
...existingConfig,
|
|
166
|
-
|
|
167
|
-
...existingConfig.
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
...existingConfig.models?.providers,
|
|
171
|
-
...providers,
|
|
172
|
-
},
|
|
86
|
+
env: {
|
|
87
|
+
...existingConfig.env,
|
|
88
|
+
DUOJIE_API_KEY: apiKey,
|
|
89
|
+
DUOJIE_BASE_URL: API_CONFIG.baseUrl,
|
|
173
90
|
},
|
|
174
91
|
agents: {
|
|
175
92
|
...existingConfig.agents,
|
|
176
93
|
defaults: {
|
|
177
94
|
...existingConfig.agents?.defaults,
|
|
178
95
|
model: {
|
|
179
|
-
primary: `${defaultModelProvider}/${defaultModel}`,
|
|
180
96
|
...existingConfig.agents?.defaults?.model,
|
|
181
|
-
|
|
182
|
-
models: {
|
|
183
|
-
...existingConfig.agents?.defaults?.models,
|
|
184
|
-
...modelAllowList,
|
|
97
|
+
primary: `duojie/${defaultModelId}`,
|
|
185
98
|
},
|
|
186
99
|
},
|
|
187
100
|
},
|
|
188
|
-
// 标记配置来源
|
|
189
101
|
_duojie: {
|
|
190
102
|
configuredAt: new Date().toISOString(),
|
|
191
|
-
version: '0.2.
|
|
103
|
+
version: '0.2.7',
|
|
104
|
+
defaultModel: defaultModelId,
|
|
105
|
+
totalModels,
|
|
192
106
|
},
|
|
193
107
|
};
|
|
194
108
|
|
|
195
|
-
// 写入配置
|
|
196
109
|
await fs.writeJson(paths.configFile, newConfig, { spaces: 2 });
|
|
197
110
|
|
|
198
111
|
return {
|
|
199
112
|
success: true,
|
|
200
|
-
message: `已配置 ${totalModels}
|
|
113
|
+
message: `已配置 ${totalModels} 个模型,默认: duojie/${defaultModelId}`,
|
|
201
114
|
configPath: paths.configFile,
|
|
202
115
|
hint: '运行 openclaw gateway restart 使配置生效',
|
|
203
116
|
};
|
|
@@ -212,65 +125,46 @@ export async function configureOpenClaw(apiKey) {
|
|
|
212
125
|
/**
|
|
213
126
|
* 检查 OpenClaw 配置状态
|
|
214
127
|
*/
|
|
215
|
-
configureOpenClaw.checkStatus = async function() {
|
|
128
|
+
configureOpenClaw.checkStatus = async function () {
|
|
216
129
|
const paths = getOpenClawConfigPaths();
|
|
217
|
-
|
|
130
|
+
|
|
218
131
|
if (await fs.pathExists(paths.configFile)) {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const config = JSON.parse(cleanContent);
|
|
226
|
-
|
|
227
|
-
if (config._duojie || config.models?.providers?.['duojie-claude']) {
|
|
228
|
-
return {
|
|
229
|
-
configured: true,
|
|
230
|
-
message: '已配置 Duojie API',
|
|
231
|
-
};
|
|
232
|
-
} else if (config.models?.providers) {
|
|
233
|
-
return {
|
|
234
|
-
configured: true,
|
|
235
|
-
message: '已配置(非 Duojie)',
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
} catch {
|
|
239
|
-
// ignore
|
|
132
|
+
const config = await readConfig(paths.configFile);
|
|
133
|
+
if (config._duojie || config.env?.DUOJIE_API_KEY) {
|
|
134
|
+
return { configured: true, message: '已配置 Duojie API' };
|
|
135
|
+
}
|
|
136
|
+
if (config.env || config.agents) {
|
|
137
|
+
return { configured: true, message: '已配置(非 Duojie)' };
|
|
240
138
|
}
|
|
241
139
|
}
|
|
242
|
-
|
|
140
|
+
|
|
243
141
|
return { configured: false };
|
|
244
142
|
};
|
|
245
143
|
|
|
246
144
|
/**
|
|
247
145
|
* 撤销 OpenClaw 配置
|
|
248
146
|
*/
|
|
249
|
-
configureOpenClaw.revoke = async function() {
|
|
147
|
+
configureOpenClaw.revoke = async function () {
|
|
250
148
|
const paths = getOpenClawConfigPaths();
|
|
251
|
-
|
|
149
|
+
|
|
252
150
|
if (await fs.pathExists(paths.configFile)) {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
delete config.models.providers['duojie-gemini'];
|
|
266
|
-
delete config.models.providers['duojie-other'];
|
|
267
|
-
}
|
|
268
|
-
delete config._duojie;
|
|
269
|
-
|
|
270
|
-
await fs.writeJson(paths.configFile, config, { spaces: 2 });
|
|
271
|
-
} catch {
|
|
272
|
-
// ignore
|
|
151
|
+
const config = await readConfig(paths.configFile);
|
|
152
|
+
|
|
153
|
+
// 移除 Duojie 注入的 env 变量
|
|
154
|
+
if (config.env) {
|
|
155
|
+
delete config.env.DUOJIE_API_KEY;
|
|
156
|
+
delete config.env.DUOJIE_BASE_URL;
|
|
157
|
+
if (Object.keys(config.env).length === 0) delete config.env;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// 若 primary 是 duojie/ 开头则清除
|
|
161
|
+
if (config.agents?.defaults?.model?.primary?.startsWith('duojie/')) {
|
|
162
|
+
delete config.agents.defaults.model.primary;
|
|
273
163
|
}
|
|
164
|
+
|
|
165
|
+
delete config._duojie;
|
|
166
|
+
|
|
167
|
+
await fs.writeJson(paths.configFile, config, { spaces: 2 });
|
|
274
168
|
}
|
|
275
169
|
};
|
|
276
170
|
|