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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "duojie-helper",
3
- "version": "0.2.4",
3
+ "version": "0.2.7",
4
4
  "description": "Duojie API 一键配置助手 - 支持 Claude Code, Droid, OpenClaw, Cursor, Cline 等主流 AI 编程工具",
5
5
  "type": "module",
6
6
  "bin": {
@@ -50,23 +50,19 @@ export async function configureClaudeCode(apiKey) {
50
50
  const models = getModels();
51
51
  const claudeModels = models.claude || [];
52
52
 
53
- // 查找各类型模型(优先查找 kiro 版本)
53
+ // 查找各类型模型(优先查找最新 kiro 版本)
54
54
  let sonnetModel = null;
55
55
  let opusModel = null;
56
56
 
57
- for (const m of claudeModels) {
58
- const id = m.id.toLowerCase();
59
- // 优先使用 kiro 版本的 opus
60
- if (id.includes('opus') && id.includes('kiro')) {
61
- opusModel = m.id;
62
- } else if (id.includes('opus') && !opusModel) {
63
- opusModel = m.id;
64
- }
65
- // sonnet 模型
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-5-kiro(或可用的 opus)
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;
@@ -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-sonnet-4-5-20250929 或 gpt-4o
51
+ 例如: claude-opus-4-6-kiro 或 gpt-4o
52
52
 
53
53
  ${chalk.cyan('步骤 5:')} 可选配置:
54
54
  • ${chalk.green('Max Tokens:')} 8192 (推荐)
@@ -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 = 'gpt-4o';
147
+ existingConfig.model = 'claude-opus-4-6-kiro';
148
148
  }
149
149
 
150
150
  // 生成 TOML 内容
@@ -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-sonnet-4-5-20250929
50
+ 例如: claude-opus-4-6-kiro
51
51
  或: gpt-4o
52
52
 
53
53
  ${chalk.cyan('步骤 4:')} 点击保存,然后在模型列表中选择刚添加的模型
@@ -21,7 +21,7 @@ function getDroidConfigPaths() {
21
21
  */
22
22
  function generateDisplayName(modelId) {
23
23
  // 将 model id 转换为友好名称
24
- // claude-opus-4-5-kiro -> Opus 4.5 Kiro
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
 
@@ -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
- 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
- // 确定默认模型 - 优先使用 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
- models: {
167
- ...existingConfig.models,
168
- mode: 'merge',
169
- providers: {
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.0',
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} 个模型 → ${paths.configFile}`,
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
- try {
220
- const content = await fs.readFile(paths.configFile, 'utf-8');
221
- const cleanContent = content
222
- .replace(/\/\/.*$/gm, '')
223
- .replace(/\/\*[\s\S]*?\*\//g, '')
224
- .replace(/,(\s*[}\]])/g, '$1');
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
- try {
254
- const content = await fs.readFile(paths.configFile, 'utf-8');
255
- const cleanContent = content
256
- .replace(/\/\/.*$/gm, '')
257
- .replace(/\/\*[\s\S]*?\*\//g, '')
258
- .replace(/,(\s*[}\]])/g, '$1');
259
- const config = JSON.parse(cleanContent);
260
-
261
- // 删除 Duojie 相关配置
262
- if (config.models?.providers) {
263
- delete config.models.providers['duojie-claude'];
264
- delete config.models.providers['duojie-gpt'];
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