duojie-helper 0.2.7 → 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 +108 -52
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "duojie-helper",
3
- "version": "0.2.7",
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,7 +5,7 @@ import { API_CONFIG, getModels } from '../index.js';
5
5
 
6
6
  /**
7
7
  * 获取 OpenClaw 配置路径
8
- * 支持 macOS / Linux / Windows
8
+ * 支持 macOS / Linux / Windows(os.homedir() 跨平台处理)
9
9
  */
10
10
  function getOpenClawConfigPaths() {
11
11
  const home = os.homedir();
@@ -32,39 +32,28 @@ async function readConfig(filePath) {
32
32
  }
33
33
 
34
34
  /**
35
- * 从模型列表中选出最优默认模型 ID
36
- * 优先级:opus-4-6-kiro > opus-4-6 > opus+kiro > opus > sonnet > 第一个
35
+ * 将模型对象转换为 OpenClaw models.providers 格式
37
36
  */
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;
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,
46
+ };
59
47
  }
60
48
 
61
49
  /**
62
50
  * 配置 OpenClaw
63
51
  *
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>"
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
68
57
  */
69
58
  export async function configureOpenClaw(apiKey) {
70
59
  const paths = getOpenClawConfigPaths();
@@ -77,32 +66,105 @@ export async function configureOpenClaw(apiKey) {
77
66
  : {};
78
67
 
79
68
  const models = getModels();
80
- const defaultModelId = pickDefaultModel(models);
81
- const totalModels = Object.values(models).reduce((s, arr) => s + arr.length, 0);
69
+
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));
75
+
76
+ // 构建 providers(按模型类型分组,api 协议对应文档规范)
77
+ const providers = {};
78
+
79
+ if (claudeModels.length > 0) {
80
+ providers['duojie-claude'] = {
81
+ baseUrl: API_CONFIG.baseUrl,
82
+ apiKey,
83
+ api: 'anthropic-messages',
84
+ models: claudeModels,
85
+ };
86
+ }
87
+ if (gptModels.length > 0) {
88
+ providers['duojie-gpt'] = {
89
+ baseUrl: API_CONFIG.baseUrl,
90
+ apiKey,
91
+ api: models.gpt[0]?.api === 'openai-responses' ? 'openai-responses' : 'openai-completions',
92
+ models: gptModels,
93
+ };
94
+ }
95
+ if (geminiModels.length > 0) {
96
+ providers['duojie-gemini'] = {
97
+ baseUrl: API_CONFIG.baseUrl,
98
+ apiKey,
99
+ api: 'openai-completions',
100
+ models: geminiModels,
101
+ };
102
+ }
103
+ if (otherModels.length > 0) {
104
+ providers['duojie-other'] = {
105
+ baseUrl: API_CONFIG.baseUrl,
106
+ apiKey,
107
+ api: 'openai-completions',
108
+ models: otherModels,
109
+ };
110
+ }
111
+
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
+
116
+ if (claudeModels.length > 0) {
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;
126
+ } else if (gptModels.length > 0) {
127
+ defaultProvider = 'duojie-gpt';
128
+ defaultModelId = gptModels[0].id;
129
+ }
130
+
131
+ // 构建模型 allowlist(用于 /model 切换)
132
+ const modelAllowList = {};
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 };
137
+
138
+ const totalModels = claudeModels.length + gptModels.length + geminiModels.length + otherModels.length;
82
139
 
83
140
  // 合并配置,保留用户已有的非 Duojie 设置
84
141
  const newConfig = {
85
142
  ...existingConfig,
86
- env: {
87
- ...existingConfig.env,
88
- DUOJIE_API_KEY: apiKey,
89
- DUOJIE_BASE_URL: API_CONFIG.baseUrl,
143
+ models: {
144
+ ...existingConfig.models,
145
+ mode: 'merge',
146
+ providers: {
147
+ ...existingConfig.models?.providers,
148
+ ...providers,
149
+ },
90
150
  },
91
151
  agents: {
92
152
  ...existingConfig.agents,
93
153
  defaults: {
94
154
  ...existingConfig.agents?.defaults,
95
155
  model: {
156
+ primary: `${defaultProvider}/${defaultModelId}`,
96
157
  ...existingConfig.agents?.defaults?.model,
97
- primary: `duojie/${defaultModelId}`,
158
+ },
159
+ models: {
160
+ ...existingConfig.agents?.defaults?.models,
161
+ ...modelAllowList,
98
162
  },
99
163
  },
100
164
  },
101
165
  _duojie: {
102
166
  configuredAt: new Date().toISOString(),
103
- version: '0.2.7',
104
- defaultModel: defaultModelId,
105
- totalModels,
167
+ version: '0.2.8',
106
168
  },
107
169
  };
108
170
 
@@ -110,7 +172,7 @@ export async function configureOpenClaw(apiKey) {
110
172
 
111
173
  return {
112
174
  success: true,
113
- message: `已配置 ${totalModels} 个模型,默认: duojie/${defaultModelId}`,
175
+ message: `已配置 ${totalModels} 个模型 → ${paths.configFile}`,
114
176
  configPath: paths.configFile,
115
177
  hint: '运行 openclaw gateway restart 使配置生效',
116
178
  };
@@ -130,10 +192,10 @@ configureOpenClaw.checkStatus = async function () {
130
192
 
131
193
  if (await fs.pathExists(paths.configFile)) {
132
194
  const config = await readConfig(paths.configFile);
133
- if (config._duojie || config.env?.DUOJIE_API_KEY) {
195
+ if (config._duojie || config.models?.providers?.['duojie-claude']) {
134
196
  return { configured: true, message: '已配置 Duojie API' };
135
197
  }
136
- if (config.env || config.agents) {
198
+ if (config.models?.providers) {
137
199
  return { configured: true, message: '已配置(非 Duojie)' };
138
200
  }
139
201
  }
@@ -150,18 +212,12 @@ configureOpenClaw.revoke = async function () {
150
212
  if (await fs.pathExists(paths.configFile)) {
151
213
  const config = await readConfig(paths.configFile);
152
214
 
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;
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'];
163
220
  }
164
-
165
221
  delete config._duojie;
166
222
 
167
223
  await fs.writeJson(paths.configFile, config, { spaces: 2 });