duojie-helper 0.2.6 → 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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/tools/openclaw.js +92 -204
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "duojie-helper",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "Duojie API 一键配置助手 - 支持 Claude Code, Droid, OpenClaw, Cursor, Cline 等主流 AI 编程工具",
5
5
  "type": "module",
6
6
  "bin": {
@@ -5,205 +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
- 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
- }
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
- // 构建 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
- }));
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
- // 确定默认模型 - 优先使用 opus-4-6-kiro
134
- let defaultModelProvider = 'duojie-claude';
135
- let defaultModel = 'claude-opus-4-6-kiro';
136
-
137
- 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;
146
- } else if (gptModels.length > 0) {
147
- defaultModelProvider = 'duojie-gpt';
148
- defaultModel = gptModels[0].id;
149
- }
150
-
151
- // 构建模型允许列表
152
- 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
- }
165
-
166
- // 统计配置的模型数量
167
- const totalModels = claudeModels.length + gptModels.length + geminiModels.length + otherModels.length;
168
-
169
- // 合并配置(不覆盖用户其他配置)
83
+ // 合并配置,保留用户已有的非 Duojie 设置
170
84
  const newConfig = {
171
85
  ...existingConfig,
172
- models: {
173
- ...existingConfig.models,
174
- mode: 'merge',
175
- providers: {
176
- ...existingConfig.models?.providers,
177
- ...providers,
178
- },
86
+ env: {
87
+ ...existingConfig.env,
88
+ DUOJIE_API_KEY: apiKey,
89
+ DUOJIE_BASE_URL: API_CONFIG.baseUrl,
179
90
  },
180
91
  agents: {
181
92
  ...existingConfig.agents,
182
93
  defaults: {
183
94
  ...existingConfig.agents?.defaults,
184
95
  model: {
185
- primary: `${defaultModelProvider}/${defaultModel}`,
186
96
  ...existingConfig.agents?.defaults?.model,
187
- },
188
- models: {
189
- ...existingConfig.agents?.defaults?.models,
190
- ...modelAllowList,
97
+ primary: `duojie/${defaultModelId}`,
191
98
  },
192
99
  },
193
100
  },
194
- // 标记配置来源
195
101
  _duojie: {
196
102
  configuredAt: new Date().toISOString(),
197
- version: '0.2.0',
103
+ version: '0.2.7',
104
+ defaultModel: defaultModelId,
105
+ totalModels,
198
106
  },
199
107
  };
200
108
 
201
- // 写入配置
202
109
  await fs.writeJson(paths.configFile, newConfig, { spaces: 2 });
203
110
 
204
111
  return {
205
112
  success: true,
206
- message: `已配置 ${totalModels} 个模型 → ${paths.configFile}`,
113
+ message: `已配置 ${totalModels} 个模型,默认: duojie/${defaultModelId}`,
207
114
  configPath: paths.configFile,
208
115
  hint: '运行 openclaw gateway restart 使配置生效',
209
116
  };
@@ -218,65 +125,46 @@ export async function configureOpenClaw(apiKey) {
218
125
  /**
219
126
  * 检查 OpenClaw 配置状态
220
127
  */
221
- configureOpenClaw.checkStatus = async function() {
128
+ configureOpenClaw.checkStatus = async function () {
222
129
  const paths = getOpenClawConfigPaths();
223
-
130
+
224
131
  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
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)' };
246
138
  }
247
139
  }
248
-
140
+
249
141
  return { configured: false };
250
142
  };
251
143
 
252
144
  /**
253
145
  * 撤销 OpenClaw 配置
254
146
  */
255
- configureOpenClaw.revoke = async function() {
147
+ configureOpenClaw.revoke = async function () {
256
148
  const paths = getOpenClawConfigPaths();
257
-
149
+
258
150
  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
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;
279
163
  }
164
+
165
+ delete config._duojie;
166
+
167
+ await fs.writeJson(paths.configFile, config, { spaces: 2 });
280
168
  }
281
169
  };
282
170