duojie-helper 0.2.6 → 0.2.8
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/openclaw.js +98 -154
package/package.json
CHANGED
package/src/tools/openclaw.js
CHANGED
|
@@ -5,168 +5,139 @@ import { API_CONFIG, getModels } from '../index.js';
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* 获取 OpenClaw 配置路径
|
|
8
|
+
* 支持 macOS / Linux / Windows(os.homedir() 跨平台处理)
|
|
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
|
-
|
|
15
|
+
};
|
|
16
|
+
}
|
|
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
|
+
* 将模型对象转换为 OpenClaw models.providers 格式
|
|
36
|
+
*/
|
|
37
|
+
function buildModelEntry(m, contextWindow, maxTokens) {
|
|
38
|
+
return {
|
|
39
|
+
id: m.id,
|
|
40
|
+
name: m.name || m.id,
|
|
41
|
+
reasoning: false,
|
|
42
|
+
input: ['text', 'image'],
|
|
43
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
44
|
+
contextWindow,
|
|
45
|
+
maxTokens,
|
|
15
46
|
};
|
|
16
47
|
}
|
|
17
48
|
|
|
18
49
|
/**
|
|
19
50
|
* 配置 OpenClaw
|
|
20
|
-
*
|
|
51
|
+
*
|
|
52
|
+
* 使用官方 models.providers 自定义 provider 格式:
|
|
53
|
+
* models.providers.<name>.baseUrl / apiKey / api / models[]
|
|
54
|
+
* agents.defaults.model.primary → "<provider>/<model-id>"
|
|
55
|
+
*
|
|
56
|
+
* 参考文档:https://docs.openclaw.ai/gateway/configuration-reference#custom-providers-and-base-urls
|
|
21
57
|
*/
|
|
22
58
|
export async function configureOpenClaw(apiKey) {
|
|
23
59
|
const paths = getOpenClawConfigPaths();
|
|
24
|
-
|
|
60
|
+
|
|
25
61
|
try {
|
|
26
|
-
// 确保目录存在
|
|
27
62
|
await fs.ensureDir(paths.configDir);
|
|
28
63
|
|
|
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
|
-
}
|
|
64
|
+
const existingConfig = await fs.pathExists(paths.configFile)
|
|
65
|
+
? await readConfig(paths.configFile)
|
|
66
|
+
: {};
|
|
44
67
|
|
|
45
|
-
// 获取模型列表
|
|
46
68
|
const models = getModels();
|
|
47
69
|
|
|
48
|
-
//
|
|
49
|
-
const claudeModels = (models.claude || []).map(m => (
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
}));
|
|
70
|
+
// 构建各类模型列表
|
|
71
|
+
const claudeModels = (models.claude || []).map(m => buildModelEntry(m, 200000, 64000));
|
|
72
|
+
const gptModels = (models.gpt || []).map(m => buildModelEntry(m, 128000, 16384));
|
|
73
|
+
const geminiModels = (models.gemini || []).map(m => buildModelEntry(m, 1000000, 65536));
|
|
74
|
+
const otherModels = (models.other || []).map(m => buildModelEntry(m, 128000, 16384));
|
|
91
75
|
|
|
92
|
-
// 构建 providers
|
|
76
|
+
// 构建 providers(按模型类型分组,api 协议对应文档规范)
|
|
93
77
|
const providers = {};
|
|
94
78
|
|
|
95
79
|
if (claudeModels.length > 0) {
|
|
96
80
|
providers['duojie-claude'] = {
|
|
97
81
|
baseUrl: API_CONFIG.baseUrl,
|
|
98
|
-
apiKey
|
|
82
|
+
apiKey,
|
|
99
83
|
api: 'anthropic-messages',
|
|
100
84
|
models: claudeModels,
|
|
101
85
|
};
|
|
102
86
|
}
|
|
103
|
-
|
|
104
87
|
if (gptModels.length > 0) {
|
|
105
|
-
// GPT 使用 openai-responses (如果支持) 或 openai-completions
|
|
106
|
-
const gptApi = models.gpt[0]?.api || 'openai-responses';
|
|
107
88
|
providers['duojie-gpt'] = {
|
|
108
89
|
baseUrl: API_CONFIG.baseUrl,
|
|
109
|
-
apiKey
|
|
110
|
-
api:
|
|
90
|
+
apiKey,
|
|
91
|
+
api: models.gpt[0]?.api === 'openai-responses' ? 'openai-responses' : 'openai-completions',
|
|
111
92
|
models: gptModels,
|
|
112
93
|
};
|
|
113
94
|
}
|
|
114
|
-
|
|
115
95
|
if (geminiModels.length > 0) {
|
|
116
96
|
providers['duojie-gemini'] = {
|
|
117
97
|
baseUrl: API_CONFIG.baseUrl,
|
|
118
|
-
apiKey
|
|
119
|
-
api: '
|
|
98
|
+
apiKey,
|
|
99
|
+
api: 'openai-completions',
|
|
120
100
|
models: geminiModels,
|
|
121
101
|
};
|
|
122
102
|
}
|
|
123
|
-
|
|
124
103
|
if (otherModels.length > 0) {
|
|
125
104
|
providers['duojie-other'] = {
|
|
126
105
|
baseUrl: API_CONFIG.baseUrl,
|
|
127
|
-
apiKey
|
|
106
|
+
apiKey,
|
|
128
107
|
api: 'openai-completions',
|
|
129
108
|
models: otherModels,
|
|
130
109
|
};
|
|
131
110
|
}
|
|
132
111
|
|
|
133
|
-
//
|
|
134
|
-
let
|
|
135
|
-
let
|
|
136
|
-
|
|
112
|
+
// 选出默认模型,优先级:opus-4-6-kiro > opus-4-6 > opus+kiro > opus > sonnet > 第一个
|
|
113
|
+
let defaultProvider = 'duojie-claude';
|
|
114
|
+
let defaultModelId = 'claude-opus-4-6-kiro';
|
|
115
|
+
|
|
137
116
|
if (claudeModels.length > 0) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
117
|
+
defaultProvider = 'duojie-claude';
|
|
118
|
+
defaultModelId = (
|
|
119
|
+
claudeModels.find(m => m.id.includes('opus-4-6') && m.id.includes('kiro')) ||
|
|
120
|
+
claudeModels.find(m => m.id.includes('opus-4-6')) ||
|
|
121
|
+
claudeModels.find(m => m.id.includes('opus') && m.id.includes('kiro')) ||
|
|
122
|
+
claudeModels.find(m => m.id.includes('opus')) ||
|
|
123
|
+
claudeModels.find(m => m.id.includes('sonnet')) ||
|
|
124
|
+
claudeModels[0]
|
|
125
|
+
).id;
|
|
146
126
|
} else if (gptModels.length > 0) {
|
|
147
|
-
|
|
148
|
-
|
|
127
|
+
defaultProvider = 'duojie-gpt';
|
|
128
|
+
defaultModelId = gptModels[0].id;
|
|
149
129
|
}
|
|
150
130
|
|
|
151
|
-
//
|
|
131
|
+
// 构建模型 allowlist(用于 /model 切换)
|
|
152
132
|
const modelAllowList = {};
|
|
153
|
-
for (const m of claudeModels) {
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
for (const m of
|
|
157
|
-
modelAllowList[`duojie-gpt/${m.id}`] = { alias: m.name };
|
|
158
|
-
}
|
|
159
|
-
for (const m of geminiModels) {
|
|
160
|
-
modelAllowList[`duojie-gemini/${m.id}`] = { alias: m.name };
|
|
161
|
-
}
|
|
162
|
-
for (const m of otherModels) {
|
|
163
|
-
modelAllowList[`duojie-other/${m.id}`] = { alias: m.name };
|
|
164
|
-
}
|
|
133
|
+
for (const m of claudeModels) modelAllowList[`duojie-claude/${m.id}`] = { alias: m.name };
|
|
134
|
+
for (const m of gptModels) modelAllowList[`duojie-gpt/${m.id}`] = { alias: m.name };
|
|
135
|
+
for (const m of geminiModels) modelAllowList[`duojie-gemini/${m.id}`] = { alias: m.name };
|
|
136
|
+
for (const m of otherModels) modelAllowList[`duojie-other/${m.id}`] = { alias: m.name };
|
|
165
137
|
|
|
166
|
-
// 统计配置的模型数量
|
|
167
138
|
const totalModels = claudeModels.length + gptModels.length + geminiModels.length + otherModels.length;
|
|
168
139
|
|
|
169
|
-
//
|
|
140
|
+
// 合并配置,保留用户已有的非 Duojie 设置
|
|
170
141
|
const newConfig = {
|
|
171
142
|
...existingConfig,
|
|
172
143
|
models: {
|
|
@@ -182,7 +153,7 @@ export async function configureOpenClaw(apiKey) {
|
|
|
182
153
|
defaults: {
|
|
183
154
|
...existingConfig.agents?.defaults,
|
|
184
155
|
model: {
|
|
185
|
-
primary: `${
|
|
156
|
+
primary: `${defaultProvider}/${defaultModelId}`,
|
|
186
157
|
...existingConfig.agents?.defaults?.model,
|
|
187
158
|
},
|
|
188
159
|
models: {
|
|
@@ -191,14 +162,12 @@ export async function configureOpenClaw(apiKey) {
|
|
|
191
162
|
},
|
|
192
163
|
},
|
|
193
164
|
},
|
|
194
|
-
// 标记配置来源
|
|
195
165
|
_duojie: {
|
|
196
166
|
configuredAt: new Date().toISOString(),
|
|
197
|
-
version: '0.2.
|
|
167
|
+
version: '0.2.8',
|
|
198
168
|
},
|
|
199
169
|
};
|
|
200
170
|
|
|
201
|
-
// 写入配置
|
|
202
171
|
await fs.writeJson(paths.configFile, newConfig, { spaces: 2 });
|
|
203
172
|
|
|
204
173
|
return {
|
|
@@ -218,65 +187,40 @@ export async function configureOpenClaw(apiKey) {
|
|
|
218
187
|
/**
|
|
219
188
|
* 检查 OpenClaw 配置状态
|
|
220
189
|
*/
|
|
221
|
-
configureOpenClaw.checkStatus = async function() {
|
|
190
|
+
configureOpenClaw.checkStatus = async function () {
|
|
222
191
|
const paths = getOpenClawConfigPaths();
|
|
223
|
-
|
|
192
|
+
|
|
224
193
|
if (await fs.pathExists(paths.configFile)) {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const config = JSON.parse(cleanContent);
|
|
232
|
-
|
|
233
|
-
if (config._duojie || config.models?.providers?.['duojie-claude']) {
|
|
234
|
-
return {
|
|
235
|
-
configured: true,
|
|
236
|
-
message: '已配置 Duojie API',
|
|
237
|
-
};
|
|
238
|
-
} else if (config.models?.providers) {
|
|
239
|
-
return {
|
|
240
|
-
configured: true,
|
|
241
|
-
message: '已配置(非 Duojie)',
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
} catch {
|
|
245
|
-
// ignore
|
|
194
|
+
const config = await readConfig(paths.configFile);
|
|
195
|
+
if (config._duojie || config.models?.providers?.['duojie-claude']) {
|
|
196
|
+
return { configured: true, message: '已配置 Duojie API' };
|
|
197
|
+
}
|
|
198
|
+
if (config.models?.providers) {
|
|
199
|
+
return { configured: true, message: '已配置(非 Duojie)' };
|
|
246
200
|
}
|
|
247
201
|
}
|
|
248
|
-
|
|
202
|
+
|
|
249
203
|
return { configured: false };
|
|
250
204
|
};
|
|
251
205
|
|
|
252
206
|
/**
|
|
253
207
|
* 撤销 OpenClaw 配置
|
|
254
208
|
*/
|
|
255
|
-
configureOpenClaw.revoke = async function() {
|
|
209
|
+
configureOpenClaw.revoke = async function () {
|
|
256
210
|
const paths = getOpenClawConfigPaths();
|
|
257
|
-
|
|
211
|
+
|
|
258
212
|
if (await fs.pathExists(paths.configFile)) {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
// 删除 Duojie 相关配置
|
|
268
|
-
if (config.models?.providers) {
|
|
269
|
-
delete config.models.providers['duojie-claude'];
|
|
270
|
-
delete config.models.providers['duojie-gpt'];
|
|
271
|
-
delete config.models.providers['duojie-gemini'];
|
|
272
|
-
delete config.models.providers['duojie-other'];
|
|
273
|
-
}
|
|
274
|
-
delete config._duojie;
|
|
275
|
-
|
|
276
|
-
await fs.writeJson(paths.configFile, config, { spaces: 2 });
|
|
277
|
-
} catch {
|
|
278
|
-
// ignore
|
|
213
|
+
const config = await readConfig(paths.configFile);
|
|
214
|
+
|
|
215
|
+
if (config.models?.providers) {
|
|
216
|
+
delete config.models.providers['duojie-claude'];
|
|
217
|
+
delete config.models.providers['duojie-gpt'];
|
|
218
|
+
delete config.models.providers['duojie-gemini'];
|
|
219
|
+
delete config.models.providers['duojie-other'];
|
|
279
220
|
}
|
|
221
|
+
delete config._duojie;
|
|
222
|
+
|
|
223
|
+
await fs.writeJson(paths.configFile, config, { spaces: 2 });
|
|
280
224
|
}
|
|
281
225
|
};
|
|
282
226
|
|