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 +5 -1
- package/package.json +1 -1
- package/src/tools/openclaw.js +128 -65
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(
|
|
18
|
+
.version(pkg.version);
|
|
15
19
|
|
|
16
20
|
// 默认命令 - 交互式配置
|
|
17
21
|
program
|
package/package.json
CHANGED
package/src/tools/openclaw.js
CHANGED
|
@@ -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 =>
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
127
|
+
const allModels = [...claudeModels, ...gptModels, ...geminiModels, ...otherModels];
|
|
128
|
+
|
|
129
|
+
// 构建 providers(按 api 协议分组,避免在同一个 provider 混用不同协议)
|
|
77
130
|
const providers = {};
|
|
131
|
+
const modelIdToProvider = new Map();
|
|
78
132
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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 =
|
|
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 = '
|
|
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
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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 =
|
|
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
|
-
...
|
|
196
|
+
...baseConfig,
|
|
147
197
|
models: {
|
|
148
|
-
...
|
|
198
|
+
...baseConfig.models,
|
|
149
199
|
mode: 'merge',
|
|
150
200
|
providers: {
|
|
151
|
-
...
|
|
201
|
+
...existingProviders,
|
|
152
202
|
...providers,
|
|
153
203
|
},
|
|
154
204
|
},
|
|
155
205
|
agents: {
|
|
156
|
-
...
|
|
206
|
+
...baseConfig.agents,
|
|
157
207
|
defaults: {
|
|
158
|
-
...
|
|
208
|
+
...baseConfig.agents?.defaults,
|
|
159
209
|
model: {
|
|
160
|
-
...
|
|
210
|
+
...baseConfig.agents?.defaults?.model,
|
|
161
211
|
primary: `${defaultProvider}/${defaultModelId}`,
|
|
162
212
|
},
|
|
163
213
|
models: {
|
|
164
|
-
...
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
}
|