duojie-helper 0.2.16 → 0.2.17

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/bin/cli.js CHANGED
@@ -4,14 +4,18 @@ import { Command } from 'commander';
4
4
  import inquirer from 'inquirer';
5
5
  import chalk from 'chalk';
6
6
  import ora from 'ora';
7
+ import { createRequire } from 'module';
7
8
  import { configureAll, configureTool, listTools, showStatus, revokeKey, fetchModels, getModels } from '../src/index.js';
8
9
 
10
+ const require = createRequire(import.meta.url);
11
+ const pkg = require('../package.json');
12
+
9
13
  const program = new Command();
10
14
 
11
15
  program
12
16
  .name('duojie')
13
17
  .description('🎮 Duojie API 一键配置助手 - 支持主流 AI 编程工具')
14
- .version('0.2.3');
18
+ .version(pkg.version);
15
19
 
16
20
  // 默认命令 - 交互式配置
17
21
  program
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "duojie-helper",
3
- "version": "0.2.16",
3
+ "version": "0.2.17",
4
4
  "description": "Duojie API 一键配置助手 - 支持 Claude Code, Droid, OpenClaw, Cursor, Cline 等主流 AI 编程工具",
5
5
  "type": "module",
6
6
  "bin": {
@@ -3,6 +3,8 @@ import path from 'path';
3
3
  import os from 'os';
4
4
  import { getModels, getApiBaseForProtocol } from '../index.js';
5
5
 
6
+ const DUOJIE_PROVIDER_PREFIX = 'duojie-';
7
+
6
8
  /**
7
9
  * 获取 OpenClaw 配置路径
8
10
  * 支持 macOS / Linux / Windows(os.homedir() 跨平台处理)
@@ -46,6 +48,34 @@ function buildModelEntry(m, contextWindow, maxTokens) {
46
48
  };
47
49
  }
48
50
 
51
+ function canonicalizeApi(api) {
52
+ const normalized = `${api || ''}`.toLowerCase().trim();
53
+ if (!normalized) return 'openai-completions';
54
+ if (normalized === 'openai-response') return 'openai-responses';
55
+ if (normalized === 'openai-completion') return 'openai-completions';
56
+ if (normalized === 'openai') return 'openai-completions';
57
+ return normalized;
58
+ }
59
+
60
+ function providerNameForApi(api) {
61
+ const normalized = canonicalizeApi(api);
62
+ if (normalized === 'anthropic-messages') return 'duojie-claude';
63
+ if (normalized === 'openai-completions') return 'duojie-openai';
64
+ if (normalized === 'openai-responses') return 'duojie-openai-responses';
65
+ return `duojie-${normalized.replace(/[^a-z0-9]+/g, '-')}`;
66
+ }
67
+
68
+ function stripDuojieKeysFromObject(obj) {
69
+ if (!obj || typeof obj !== 'object') return {};
70
+ const next = { ...obj };
71
+ for (const key of Object.keys(next)) {
72
+ if (`${key}`.startsWith(DUOJIE_PROVIDER_PREFIX)) {
73
+ delete next[key];
74
+ }
75
+ }
76
+ return next;
77
+ }
78
+
49
79
  /**
50
80
  * 配置 OpenClaw
51
81
  *
@@ -65,60 +95,67 @@ export async function configureOpenClaw(apiKey) {
65
95
  ? await readConfig(paths.configFile)
66
96
  : {};
67
97
 
98
+ // OpenClaw 配置会做 schema 校验:根级未知字段可能导致校验失败
99
+ // 旧版本写入过 `_duojie`,这里显式剔除
100
+ const { _duojie: _ignoredDuojie, ...baseConfig } = existingConfig || {};
101
+ void _ignoredDuojie;
102
+
68
103
  const models = getModels();
69
104
 
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));
105
+ // 构建各类模型列表(保留 api 协议,后续按 api 拆分 providers)
106
+ const claudeModels = (models.claude || []).map(m => ({
107
+ ...m,
108
+ api: canonicalizeApi(m.api || 'anthropic-messages'),
109
+ _entry: buildModelEntry(m, 200000, 64000),
110
+ }));
111
+ const gptModels = (models.gpt || []).map(m => ({
112
+ ...m,
113
+ api: canonicalizeApi(m.api || 'openai-completions'),
114
+ _entry: buildModelEntry(m, 128000, 16384),
115
+ }));
116
+ const geminiModels = (models.gemini || []).map(m => ({
117
+ ...m,
118
+ api: canonicalizeApi(m.api || 'openai-completions'),
119
+ _entry: buildModelEntry(m, 1000000, 65536),
120
+ }));
121
+ const otherModels = (models.other || []).map(m => ({
122
+ ...m,
123
+ api: canonicalizeApi(m.api || 'openai-completions'),
124
+ _entry: buildModelEntry(m, 128000, 16384),
125
+ }));
75
126
 
76
- // 构建 providers(按模型类型分组,api 协议对应文档规范)
127
+ const allModels = [...claudeModels, ...gptModels, ...geminiModels, ...otherModels];
128
+
129
+ // 构建 providers(按 api 协议分组,避免在同一个 provider 混用不同协议)
77
130
  const providers = {};
131
+ const modelIdToProvider = new Map();
78
132
 
79
- if (claudeModels.length > 0) {
80
- providers['duojie-claude'] = {
81
- baseUrl: getApiBaseForProtocol('anthropic-messages'),
82
- apiKey,
83
- api: 'anthropic-messages',
84
- models: claudeModels,
85
- };
86
- }
87
- if (gptModels.length > 0) {
88
- providers['duojie-gpt'] = {
89
- baseUrl: getApiBaseForProtocol('anthropic-messages'),
90
- apiKey,
91
- api: 'anthropic-messages',
92
- models: gptModels,
93
- };
94
- }
95
- if (geminiModels.length > 0) {
96
- providers['duojie-gemini'] = {
97
- baseUrl: getApiBaseForProtocol('anthropic-messages'),
98
- apiKey,
99
- api: 'anthropic-messages',
100
- models: geminiModels,
101
- };
102
- }
103
- if (otherModels.length > 0) {
104
- providers['duojie-other'] = {
105
- baseUrl: getApiBaseForProtocol('anthropic-messages'),
106
- apiKey,
107
- api: 'anthropic-messages',
108
- models: otherModels,
109
- };
133
+ for (const m of allModels) {
134
+ const api = canonicalizeApi(m.api);
135
+ const providerName = providerNameForApi(api);
136
+
137
+ if (!providers[providerName]) {
138
+ providers[providerName] = {
139
+ baseUrl: getApiBaseForProtocol(api),
140
+ apiKey,
141
+ api,
142
+ models: [],
143
+ };
144
+ }
145
+
146
+ providers[providerName].models.push(m._entry);
147
+ modelIdToProvider.set(m.id, providerName);
110
148
  }
111
149
 
112
150
  // 默认模型:优先 gpt-5.2(如存在)
113
- const gpt52 = (models.gpt || []).find(m => m.id === 'gpt-5.2')?.id || null;
114
- let defaultProvider = 'duojie-claude';
151
+ const gpt52 = gptModels.find(m => m.id === 'gpt-5.2')?.id || null;
152
+ let defaultProvider = modelIdToProvider.get('claude-opus-4-6-kiro') || 'duojie-claude';
115
153
  let defaultModelId = 'claude-opus-4-6-kiro';
116
154
 
117
155
  if (gpt52) {
118
- defaultProvider = 'duojie-gpt';
156
+ defaultProvider = modelIdToProvider.get(gpt52) || providerNameForApi('openai-completions');
119
157
  defaultModelId = gpt52;
120
158
  } else if (claudeModels.length > 0) {
121
- defaultProvider = 'duojie-claude';
122
159
  defaultModelId = (
123
160
  claudeModels.find(m => m.id.includes('opus-4-6') && m.id.includes('kiro')) ||
124
161
  claudeModels.find(m => m.id.includes('opus-4-6')) ||
@@ -127,49 +164,58 @@ export async function configureOpenClaw(apiKey) {
127
164
  claudeModels.find(m => m.id.includes('sonnet')) ||
128
165
  claudeModels[0]
129
166
  ).id;
167
+ defaultProvider = modelIdToProvider.get(defaultModelId) || 'duojie-claude';
130
168
  } else if (gptModels.length > 0) {
131
- defaultProvider = 'duojie-gpt';
132
169
  defaultModelId = gptModels[0].id;
170
+ defaultProvider = modelIdToProvider.get(defaultModelId) || providerNameForApi(gptModels[0].api);
171
+ } else if (geminiModels.length > 0) {
172
+ defaultModelId = geminiModels[0].id;
173
+ defaultProvider = modelIdToProvider.get(defaultModelId) || providerNameForApi(geminiModels[0].api);
174
+ } else if (otherModels.length > 0) {
175
+ defaultModelId = otherModels[0].id;
176
+ defaultProvider = modelIdToProvider.get(defaultModelId) || providerNameForApi(otherModels[0].api);
177
+ } else if (allModels.length > 0) {
178
+ defaultModelId = allModels[0].id;
179
+ defaultProvider = modelIdToProvider.get(defaultModelId) || providerNameForApi(allModels[0].api);
133
180
  }
134
181
 
135
182
  // 构建模型 allowlist(用于 /model 切换)
136
183
  const modelAllowList = {};
137
- for (const m of claudeModels) modelAllowList[`duojie-claude/${m.id}`] = { alias: m.name };
138
- for (const m of gptModels) modelAllowList[`duojie-gpt/${m.id}`] = { alias: m.name };
139
- for (const m of geminiModels) modelAllowList[`duojie-gemini/${m.id}`] = { alias: m.name };
140
- for (const m of otherModels) modelAllowList[`duojie-other/${m.id}`] = { alias: m.name };
184
+ for (const m of allModels) {
185
+ const providerName = modelIdToProvider.get(m.id) || providerNameForApi(m.api);
186
+ modelAllowList[`${providerName}/${m.id}`] = { alias: m.name || m.id };
187
+ }
141
188
 
142
- const totalModels = claudeModels.length + gptModels.length + geminiModels.length + otherModels.length;
189
+ const totalModels = allModels.length;
143
190
 
144
191
  // 合并配置,保留用户已有的非 Duojie 设置
192
+ const existingProviders = stripDuojieKeysFromObject(baseConfig.models?.providers);
193
+ const existingAllowList = stripDuojieKeysFromObject(baseConfig.agents?.defaults?.models);
194
+
145
195
  const newConfig = {
146
- ...existingConfig,
196
+ ...baseConfig,
147
197
  models: {
148
- ...existingConfig.models,
198
+ ...baseConfig.models,
149
199
  mode: 'merge',
150
200
  providers: {
151
- ...existingConfig.models?.providers,
201
+ ...existingProviders,
152
202
  ...providers,
153
203
  },
154
204
  },
155
205
  agents: {
156
- ...existingConfig.agents,
206
+ ...baseConfig.agents,
157
207
  defaults: {
158
- ...existingConfig.agents?.defaults,
208
+ ...baseConfig.agents?.defaults,
159
209
  model: {
160
- ...existingConfig.agents?.defaults?.model,
210
+ ...baseConfig.agents?.defaults?.model,
161
211
  primary: `${defaultProvider}/${defaultModelId}`,
162
212
  },
163
213
  models: {
164
- ...existingConfig.agents?.defaults?.models,
214
+ ...existingAllowList,
165
215
  ...modelAllowList,
166
216
  },
167
217
  },
168
218
  },
169
- _duojie: {
170
- configuredAt: new Date().toISOString(),
171
- version: '0.2.8',
172
- },
173
219
  };
174
220
 
175
221
  await fs.writeJson(paths.configFile, newConfig, { spaces: 2 });
@@ -196,10 +242,11 @@ configureOpenClaw.checkStatus = async function () {
196
242
 
197
243
  if (await fs.pathExists(paths.configFile)) {
198
244
  const config = await readConfig(paths.configFile);
199
- if (config._duojie || config.models?.providers?.['duojie-claude']) {
245
+ const providerKeys = Object.keys(config.models?.providers || {});
246
+ if (providerKeys.some(k => `${k}`.startsWith(DUOJIE_PROVIDER_PREFIX))) {
200
247
  return { configured: true, message: '已配置 Duojie API' };
201
248
  }
202
- if (config.models?.providers) {
249
+ if (providerKeys.length > 0) {
203
250
  return { configured: true, message: '已配置(非 Duojie)' };
204
251
  }
205
252
  }
@@ -217,12 +264,28 @@ configureOpenClaw.revoke = async function () {
217
264
  const config = await readConfig(paths.configFile);
218
265
 
219
266
  if (config.models?.providers) {
220
- delete config.models.providers['duojie-claude'];
221
- delete config.models.providers['duojie-gpt'];
222
- delete config.models.providers['duojie-gemini'];
223
- delete config.models.providers['duojie-other'];
267
+ for (const key of Object.keys(config.models.providers)) {
268
+ if (`${key}`.startsWith(DUOJIE_PROVIDER_PREFIX)) {
269
+ delete config.models.providers[key];
270
+ }
271
+ }
272
+ }
273
+
274
+ if (config.agents?.defaults?.models) {
275
+ for (const key of Object.keys(config.agents.defaults.models)) {
276
+ if (`${key}`.startsWith(DUOJIE_PROVIDER_PREFIX)) {
277
+ delete config.agents.defaults.models[key];
278
+ }
279
+ }
280
+ }
281
+
282
+ if (config.agents?.defaults?.model?.primary?.startsWith?.(DUOJIE_PROVIDER_PREFIX)) {
283
+ delete config.agents.defaults.model.primary;
284
+ }
285
+
286
+ if (config._duojie) {
287
+ delete config._duojie;
224
288
  }
225
- delete config._duojie;
226
289
 
227
290
  await fs.writeJson(paths.configFile, config, { spaces: 2 });
228
291
  }