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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/tools/openclaw.js +98 -154
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "duojie-helper",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "Duojie API 一键配置助手 - 支持 Claude Code, Droid, OpenClaw, Cursor, Cline 等主流 AI 编程工具",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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
- agentsDir: path.join(home, '.openclaw', 'agents'),
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
- let existingConfig = {};
31
- if (await fs.pathExists(paths.configFile)) {
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
- // 构建 Duojie provider 配置 - Claude 模型 (anthropic-messages)
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
- }));
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: 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: apiKey,
110
- api: gptApi,
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: apiKey,
119
- api: 'anthropic-messages',
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: apiKey,
106
+ apiKey,
128
107
  api: 'openai-completions',
129
108
  models: otherModels,
130
109
  };
131
110
  }
132
111
 
133
- // 确定默认模型 - 优先使用 opus-4-6-kiro
134
- let defaultModelProvider = 'duojie-claude';
135
- let defaultModel = 'claude-opus-4-6-kiro';
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
- defaultModelProvider = 'duojie-claude';
139
- // 优先级:opus-4-6-kiro > opus-4-6 > opus+kiro > opus > sonnet > 第一个
140
- const opusKiro46 = claudeModels.find(m => m.id.includes('opus-4-6') && m.id.includes('kiro'));
141
- const opus46 = claudeModels.find(m => m.id.includes('opus-4-6'));
142
- const opusKiro = claudeModels.find(m => m.id.includes('opus') && m.id.includes('kiro'));
143
- const opus = claudeModels.find(m => m.id.includes('opus'));
144
- const sonnet = claudeModels.find(m => m.id.includes('sonnet'));
145
- defaultModel = opusKiro46?.id || opus46?.id || opusKiro?.id || opus?.id || sonnet?.id || claudeModels[0].id;
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
- defaultModelProvider = 'duojie-gpt';
148
- defaultModel = gptModels[0].id;
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
- modelAllowList[`duojie-claude/${m.id}`] = { alias: m.name };
155
- }
156
- for (const m of gptModels) {
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: `${defaultModelProvider}/${defaultModel}`,
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.0',
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
- try {
226
- const content = await fs.readFile(paths.configFile, 'utf-8');
227
- const cleanContent = content
228
- .replace(/\/\/.*$/gm, '')
229
- .replace(/\/\*[\s\S]*?\*\//g, '')
230
- .replace(/,(\s*[}\]])/g, '$1');
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
- try {
260
- const content = await fs.readFile(paths.configFile, 'utf-8');
261
- const cleanContent = content
262
- .replace(/\/\/.*$/gm, '')
263
- .replace(/\/\*[\s\S]*?\*\//g, '')
264
- .replace(/,(\s*[}\]])/g, '$1');
265
- const config = JSON.parse(cleanContent);
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