@tokenaut/opentoken 1.4.9 → 1.4.11
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/README.md +34 -0
- package/package.json +1 -1
- package/src/cli.js +61 -0
- package/src/config.js +1 -0
- package/src/test-connection.js +164 -0
- package/src/tools/atomcode.js +36 -1
- package/src/tools/claude.js +123 -5
- package/src/tools/claude_desktop.js +39 -3
- package/src/tools/codex.js +43 -4
- package/src/tools/hermes.js +36 -1
- package/src/tools/openclaw.js +39 -2
- package/src/tools/opencode.js +37 -2
package/README.md
CHANGED
|
@@ -146,6 +146,40 @@ opentoken cx -k <api_key> -u https://gw.opentoken.io/v1 -m <model_id>
|
|
|
146
146
|
|
|
147
147
|
---
|
|
148
148
|
|
|
149
|
+
## 连接测试
|
|
150
|
+
|
|
151
|
+
验证各工具的 API Key、Base URL、模型是否配置正确,发送最小请求检测连通性:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
opentoken cc test
|
|
155
|
+
opentoken cc test -m <model_id>
|
|
156
|
+
opentoken cdt test
|
|
157
|
+
opentoken ac test
|
|
158
|
+
opentoken hm test
|
|
159
|
+
opentoken oc test
|
|
160
|
+
opentoken od test
|
|
161
|
+
opentoken cx test
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## 查看可用模型
|
|
167
|
+
|
|
168
|
+
查看默认节点所有可用模型(自动使用已配置的 API Key):
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
opentoken models
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
使用指定 Key 查询:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
opentoken models -k <api_key>
|
|
178
|
+
opentoken models --key <api_key>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
149
183
|
## 环境
|
|
150
184
|
|
|
151
185
|
Node.js >= 14 · MIT
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -12,6 +12,8 @@ const openclaw = require('./tools/openclaw');
|
|
|
12
12
|
const opencode = require('./tools/opencode');
|
|
13
13
|
const codex = require('./tools/codex');
|
|
14
14
|
const { DEFAULT_BASE_URL } = require('./models');
|
|
15
|
+
const { fetchModelsForBaseUrl } = require('./fetch-models');
|
|
16
|
+
const { getCurrentConfig } = require('./config');
|
|
15
17
|
|
|
16
18
|
function isCcCommand(tool) {
|
|
17
19
|
return tool === 'cc' || tool === 'claude';
|
|
@@ -51,6 +53,8 @@ function printHelp() {
|
|
|
51
53
|
console.log(chalk.bold('常用:'));
|
|
52
54
|
console.log(' opentoken help | --help | -h 显示本帮助');
|
|
53
55
|
console.log(' opentoken version | --version | -v | -V 显示版本号');
|
|
56
|
+
console.log(' opentoken models 查看默认节点所有可用模型');
|
|
57
|
+
console.log(' opentoken models -k <apikey> 使用指定 Key 查看模型');
|
|
54
58
|
console.log('');
|
|
55
59
|
console.log(chalk.bold('用法:'));
|
|
56
60
|
console.log(' opentoken 进入主菜单');
|
|
@@ -97,6 +101,16 @@ function printHelp() {
|
|
|
97
101
|
console.log(' opentoken cdt --url <baseurl> 设置 Base URL(inferenceGatewayBaseUrl)');
|
|
98
102
|
console.log(' opentoken cdt -k <k> -u <u> 一键设置 Key 与 URL');
|
|
99
103
|
console.log('');
|
|
104
|
+
console.log(chalk.bold('连接测试:'));
|
|
105
|
+
console.log(' opentoken cc test 测试 Claude Code 所有模型连接');
|
|
106
|
+
console.log(' opentoken cc test -m <model> 测试 Claude Code 指定模型连接');
|
|
107
|
+
console.log(' opentoken cdt test 测试 Claude Desktop 连接');
|
|
108
|
+
console.log(' opentoken ac test 测试 Atomcode 连接');
|
|
109
|
+
console.log(' opentoken hm test 测试 Hermes 连接');
|
|
110
|
+
console.log(' opentoken oc test 测试 OpenClaw 连接');
|
|
111
|
+
console.log(' opentoken od test 测试 OpenCode 连接');
|
|
112
|
+
console.log(' opentoken cx test 测试 Codex 连接');
|
|
113
|
+
console.log('');
|
|
100
114
|
console.log(chalk.bold('快捷别名(--key / -k,--url / -u,--model / -m)'));
|
|
101
115
|
console.log('');
|
|
102
116
|
console.log(chalk.bold('示例:'));
|
|
@@ -120,6 +134,7 @@ function parseArgs(argv) {
|
|
|
120
134
|
const args = argv.slice(2);
|
|
121
135
|
const result = {
|
|
122
136
|
tool: null,
|
|
137
|
+
subcommand: null,
|
|
123
138
|
key: null,
|
|
124
139
|
url: null,
|
|
125
140
|
model: null,
|
|
@@ -134,6 +149,11 @@ function parseArgs(argv) {
|
|
|
134
149
|
i = 1;
|
|
135
150
|
}
|
|
136
151
|
|
|
152
|
+
if (args[i] && !args[i].startsWith('-')) {
|
|
153
|
+
result.subcommand = args[i].toLowerCase();
|
|
154
|
+
i++;
|
|
155
|
+
}
|
|
156
|
+
|
|
137
157
|
for (; i < args.length; i++) {
|
|
138
158
|
const a = args[i];
|
|
139
159
|
const next = args[i + 1];
|
|
@@ -228,6 +248,47 @@ async function run() {
|
|
|
228
248
|
return;
|
|
229
249
|
}
|
|
230
250
|
|
|
251
|
+
if (args.tool === 'models') {
|
|
252
|
+
const apiKey = args.key || getCurrentConfig().apiKey;
|
|
253
|
+
const maskKey = (k) => {
|
|
254
|
+
if (!k || k.length < 8) return k || '';
|
|
255
|
+
return k.slice(0, 6) + '****' + k.slice(-4);
|
|
256
|
+
};
|
|
257
|
+
console.log('');
|
|
258
|
+
console.log(chalk.bold(chalk.cyan('可用模型列表')) + chalk.gray('(' + DEFAULT_BASE_URL + ')'));
|
|
259
|
+
console.log('');
|
|
260
|
+
if (!apiKey) {
|
|
261
|
+
console.log(chalk.red(' ✗ 未配置 API Key,请使用 opentoken models -k <apikey> 或先配置 Key'));
|
|
262
|
+
console.log('');
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const result = await fetchModelsForBaseUrl(DEFAULT_BASE_URL, apiKey);
|
|
266
|
+
if (result.ok && result.models.length > 0) {
|
|
267
|
+
result.models.forEach((id) => console.log(' ' + id));
|
|
268
|
+
console.log('');
|
|
269
|
+
console.log(chalk.gray('共 ' + result.models.length + ' 个模型'));
|
|
270
|
+
} else {
|
|
271
|
+
console.log(chalk.red(' ✗ 获取失败:' + (result.error || '未知错误')));
|
|
272
|
+
}
|
|
273
|
+
console.log(chalk.gray('API Key : ' + maskKey(apiKey)));
|
|
274
|
+
console.log('');
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (args.subcommand === 'test') {
|
|
279
|
+
clearScreen();
|
|
280
|
+
printLogo();
|
|
281
|
+
if (isCcCommand(args.tool)) { await claude.test({ model: args.model }); return; }
|
|
282
|
+
if (isClaudeDesktopCommand(args.tool)) { await claudeDesktop.test(); return; }
|
|
283
|
+
if (isAtomcodeCommand(args.tool)) { await atomcode.test(); return; }
|
|
284
|
+
if (isHermesCommand(args.tool)) { await hermes.test(); return; }
|
|
285
|
+
if (isOpenclawCommand(args.tool)) { await openclaw.test(); return; }
|
|
286
|
+
if (isOpencodeCommand(args.tool)) { await opencode.test(); return; }
|
|
287
|
+
if (isCodexCommand(args.tool)) { await codex.test(); return; }
|
|
288
|
+
console.log(chalk.red('未知工具:' + args.tool));
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
231
292
|
if (isCcCommand(args.tool) && (args.key || args.url || args.model)) {
|
|
232
293
|
clearScreen();
|
|
233
294
|
printLogo();
|
package/src/config.js
CHANGED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const https = require('https');
|
|
4
|
+
const http = require('http');
|
|
5
|
+
const { URL } = require('url');
|
|
6
|
+
|
|
7
|
+
const TIMEOUT_MS = 30000;
|
|
8
|
+
|
|
9
|
+
function request(method, urlStr, headers, body) {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
const parsed = new URL(urlStr);
|
|
12
|
+
const mod = parsed.protocol === 'https:' ? https : http;
|
|
13
|
+
const opts = {
|
|
14
|
+
method,
|
|
15
|
+
hostname: parsed.hostname,
|
|
16
|
+
port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
|
|
17
|
+
path: parsed.pathname + parsed.search,
|
|
18
|
+
headers: {
|
|
19
|
+
...headers,
|
|
20
|
+
'Content-Type': 'application/json',
|
|
21
|
+
},
|
|
22
|
+
timeout: TIMEOUT_MS,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const req = mod.request(opts, (res) => {
|
|
26
|
+
let data = '';
|
|
27
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
28
|
+
res.on('end', () => {
|
|
29
|
+
resolve({ statusCode: res.statusCode, body: data });
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
req.on('error', (err) => {
|
|
34
|
+
reject(err);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
req.on('timeout', () => {
|
|
38
|
+
req.destroy();
|
|
39
|
+
reject(new Error('请求超时(' + TIMEOUT_MS / 1000 + '秒)'));
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (body) {
|
|
43
|
+
req.write(JSON.stringify(body));
|
|
44
|
+
}
|
|
45
|
+
req.end();
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function testAnthropic({ baseUrl, apiKey, model }) {
|
|
50
|
+
const start = Date.now();
|
|
51
|
+
try {
|
|
52
|
+
const url = baseUrl.replace(/\/+$/, '') + '/v1/messages';
|
|
53
|
+
const headers = {
|
|
54
|
+
'x-api-key': apiKey,
|
|
55
|
+
'anthropic-version': '2023-06-01',
|
|
56
|
+
};
|
|
57
|
+
const body = {
|
|
58
|
+
model,
|
|
59
|
+
max_tokens: 1,
|
|
60
|
+
messages: [{ role: 'user', content: 'hi' }],
|
|
61
|
+
};
|
|
62
|
+
const res = await request('POST', url, headers, body);
|
|
63
|
+
const latency = Date.now() - start;
|
|
64
|
+
|
|
65
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
66
|
+
return { ok: true, latency_ms: latency };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let errMsg = 'HTTP ' + res.statusCode;
|
|
70
|
+
try {
|
|
71
|
+
const parsed = JSON.parse(res.body);
|
|
72
|
+
if (parsed.error && parsed.error.message) {
|
|
73
|
+
errMsg += ' — ' + parsed.error.message;
|
|
74
|
+
}
|
|
75
|
+
} catch (_) {}
|
|
76
|
+
return { ok: false, error: errMsg };
|
|
77
|
+
} catch (err) {
|
|
78
|
+
return { ok: false, error: err.message };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function testOpenAI({ baseUrl, apiKey, model }) {
|
|
83
|
+
const start = Date.now();
|
|
84
|
+
try {
|
|
85
|
+
const url = baseUrl.replace(/\/+$/, '') + '/chat/completions';
|
|
86
|
+
const headers = {
|
|
87
|
+
'Authorization': 'Bearer ' + apiKey,
|
|
88
|
+
};
|
|
89
|
+
const body = {
|
|
90
|
+
model,
|
|
91
|
+
max_tokens: 1,
|
|
92
|
+
messages: [{ role: 'user', content: 'hi' }],
|
|
93
|
+
};
|
|
94
|
+
const res = await request('POST', url, headers, body);
|
|
95
|
+
const latency = Date.now() - start;
|
|
96
|
+
|
|
97
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
98
|
+
return { ok: true, latency_ms: latency };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let errMsg = 'HTTP ' + res.statusCode;
|
|
102
|
+
try {
|
|
103
|
+
const parsed = JSON.parse(res.body);
|
|
104
|
+
if (parsed.error && parsed.error.message) {
|
|
105
|
+
errMsg += ' — ' + parsed.error.message;
|
|
106
|
+
}
|
|
107
|
+
} catch (_) {}
|
|
108
|
+
return { ok: false, error: errMsg };
|
|
109
|
+
} catch (err) {
|
|
110
|
+
return { ok: false, error: err.message };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function testOpenAIResponses({ baseUrl, apiKey, model }) {
|
|
115
|
+
const start = Date.now();
|
|
116
|
+
try {
|
|
117
|
+
const url = baseUrl.replace(/\/+$/, '') + '/responses';
|
|
118
|
+
const headers = {
|
|
119
|
+
'Authorization': 'Bearer ' + apiKey,
|
|
120
|
+
};
|
|
121
|
+
const body = {
|
|
122
|
+
model,
|
|
123
|
+
input: 'hi',
|
|
124
|
+
max_output_tokens: 1,
|
|
125
|
+
};
|
|
126
|
+
const res = await request('POST', url, headers, body);
|
|
127
|
+
const latency = Date.now() - start;
|
|
128
|
+
|
|
129
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
130
|
+
return { ok: true, latency_ms: latency };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let errMsg = 'HTTP ' + res.statusCode;
|
|
134
|
+
try {
|
|
135
|
+
const parsed = JSON.parse(res.body);
|
|
136
|
+
if (parsed.error && parsed.error.message) {
|
|
137
|
+
errMsg += ' — ' + parsed.error.message;
|
|
138
|
+
}
|
|
139
|
+
} catch (_) {}
|
|
140
|
+
return { ok: false, error: errMsg };
|
|
141
|
+
} catch (err) {
|
|
142
|
+
return { ok: false, error: err.message };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
147
|
+
|
|
148
|
+
async function testWithSpinner({ label, testFn, args }) {
|
|
149
|
+
let frameIdx = 0;
|
|
150
|
+
const spinnerTimer = setInterval(() => {
|
|
151
|
+
const frame = SPINNER_FRAMES[frameIdx % SPINNER_FRAMES.length];
|
|
152
|
+
process.stdout.write('\x1b[1A\x1b[K');
|
|
153
|
+
process.stdout.write('\x1b[36m ' + frame + ' ' + label + ' 测试中...\x1b[0m\n');
|
|
154
|
+
frameIdx++;
|
|
155
|
+
}, 80);
|
|
156
|
+
|
|
157
|
+
const result = await testFn(args);
|
|
158
|
+
clearInterval(spinnerTimer);
|
|
159
|
+
|
|
160
|
+
process.stdout.write('\x1b[1A\x1b[K');
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
module.exports = { testAnthropic, testOpenAI, testOpenAIResponses, testWithSpinner };
|
package/src/tools/atomcode.js
CHANGED
|
@@ -11,6 +11,7 @@ const {
|
|
|
11
11
|
} = require('../atomcode-config');
|
|
12
12
|
const { printLogo } = require('../logo');
|
|
13
13
|
const { clearScreen } = require('../screen');
|
|
14
|
+
const { testOpenAI, testWithSpinner } = require('../test-connection');
|
|
14
15
|
|
|
15
16
|
let pendingConfig = { apiKey: null, model: null };
|
|
16
17
|
|
|
@@ -252,4 +253,38 @@ function quickSet({ key, model }) {
|
|
|
252
253
|
return true;
|
|
253
254
|
}
|
|
254
255
|
|
|
255
|
-
|
|
256
|
+
async function test() {
|
|
257
|
+
const config = getCurrentAtomcodeConfig();
|
|
258
|
+
|
|
259
|
+
if (!config.apiKey) {
|
|
260
|
+
console.log(chalk.red(' ✗ 请先配置 API Key'));
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (!config.model) {
|
|
264
|
+
console.log(chalk.red(' ✗ 请先配置模型'));
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
console.log('');
|
|
269
|
+
console.log(chalk.bold(chalk.cyan('正在测试 Atomcode 连接...')));
|
|
270
|
+
console.log(chalk.gray(' Base URL : ') + ATOMCODE_OPENTOKEN_BASE_URL);
|
|
271
|
+
console.log(chalk.gray(' 模型 : ') + config.model);
|
|
272
|
+
console.log(chalk.gray(' API Key : ') + maskApiKey(config.apiKey));
|
|
273
|
+
console.log('');
|
|
274
|
+
console.log(chalk.gray(' ' + config.model + ' 等待测试'));
|
|
275
|
+
|
|
276
|
+
const result = await testWithSpinner({
|
|
277
|
+
label: config.model,
|
|
278
|
+
testFn: testOpenAI,
|
|
279
|
+
args: { baseUrl: ATOMCODE_OPENTOKEN_BASE_URL, apiKey: config.apiKey, model: config.model },
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
if (result.ok) {
|
|
283
|
+
console.log(chalk.green(' ✓ ' + config.model + ' 连接成功 ' + result.latency_ms + 'ms'));
|
|
284
|
+
} else {
|
|
285
|
+
console.log(chalk.red(' ✗ ' + config.model + ' ' + result.error));
|
|
286
|
+
}
|
|
287
|
+
console.log('');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
module.exports = { run, quickSet, test };
|
package/src/tools/claude.js
CHANGED
|
@@ -4,9 +4,10 @@ const chalk = require('chalk');
|
|
|
4
4
|
const inquirer = require('inquirer');
|
|
5
5
|
const { API_NODES, DEFAULT_BASE_URL, CLAUDE_DEFAULT_MODEL } = require('../models');
|
|
6
6
|
const { fetchModelsForBaseUrl } = require('../fetch-models');
|
|
7
|
-
const { getCurrentConfig, applyConfig } = require('../config');
|
|
7
|
+
const { getCurrentConfig, applyConfig, readClaudeSettings } = require('../config');
|
|
8
8
|
const { printLogo } = require('../logo');
|
|
9
9
|
const { clearScreen } = require('../screen');
|
|
10
|
+
const { testAnthropic, testWithSpinner } = require('../test-connection');
|
|
10
11
|
|
|
11
12
|
let pendingConfig = { apiKey: null, baseUrl: null, model: null };
|
|
12
13
|
|
|
@@ -294,12 +295,129 @@ function quickSet({ key, url, model }) {
|
|
|
294
295
|
|
|
295
296
|
console.log('');
|
|
296
297
|
console.log(chalk.bold(chalk.cyan('Claude Code 配置已更新:')));
|
|
297
|
-
if (
|
|
298
|
-
console.log(chalk.green(' ✓ Base URL : ') +
|
|
299
|
-
console.log(chalk.green(' ✓ 模型 : ') +
|
|
298
|
+
if (toWrite.apiKey) console.log(chalk.green(' ✓ API Key : ') + maskApiKey(toWrite.apiKey));
|
|
299
|
+
if (toWrite.baseUrl) console.log(chalk.green(' ✓ Base URL : ') + toWrite.baseUrl);
|
|
300
|
+
if (toWrite.model) console.log(chalk.green(' ✓ 模型 : ') + toWrite.model);
|
|
300
301
|
console.log('');
|
|
301
302
|
|
|
302
303
|
return true;
|
|
303
304
|
}
|
|
304
305
|
|
|
305
|
-
|
|
306
|
+
async function test({ model } = {}) {
|
|
307
|
+
const config = getCurrentConfig();
|
|
308
|
+
|
|
309
|
+
if (!config.apiKey) {
|
|
310
|
+
console.log(chalk.red(' ✗ 请先配置 API Key'));
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
if (!config.baseUrl) {
|
|
314
|
+
console.log(chalk.red(' ✗ 请先配置 Base URL'));
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (model) {
|
|
319
|
+
console.log('');
|
|
320
|
+
console.log(chalk.bold(chalk.cyan('正在测试 Claude Code 连接...')));
|
|
321
|
+
console.log(chalk.gray(' Base URL : ') + config.baseUrl);
|
|
322
|
+
console.log(chalk.gray(' API Key : ') + maskApiKey(config.apiKey));
|
|
323
|
+
console.log('');
|
|
324
|
+
console.log(chalk.gray(' ' + model + ' 等待测试'));
|
|
325
|
+
|
|
326
|
+
const result = await testWithSpinner({
|
|
327
|
+
label: model,
|
|
328
|
+
testFn: testAnthropic,
|
|
329
|
+
args: { baseUrl: config.baseUrl, apiKey: config.apiKey, model },
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
if (result.ok) {
|
|
333
|
+
console.log(chalk.green(' ✓ ' + model + ' 连接成功 ' + result.latency_ms + 'ms'));
|
|
334
|
+
} else {
|
|
335
|
+
console.log(chalk.red(' ✗ ' + model + ' ' + result.error));
|
|
336
|
+
}
|
|
337
|
+
console.log('');
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const settings = readClaudeSettings();
|
|
342
|
+
const env = settings.env || {};
|
|
343
|
+
|
|
344
|
+
const modelEntries = [
|
|
345
|
+
{ label: 'Haiku', id: env.ANTHROPIC_DEFAULT_HAIKU_MODEL || 'claude-haiku-4-5' },
|
|
346
|
+
{ label: 'Sonnet', id: env.ANTHROPIC_DEFAULT_SONNET_MODEL || 'claude-sonnet-4-6' },
|
|
347
|
+
{ label: 'Opus', id: env.ANTHROPIC_DEFAULT_OPUS_MODEL || 'claude-opus-4-7' },
|
|
348
|
+
{ label: 'Fable', id: env.ANTHROPIC_DEFAULT_FABLE_MODEL || 'claude-fable-5' },
|
|
349
|
+
];
|
|
350
|
+
|
|
351
|
+
if (config.model && !modelEntries.some((e) => e.id === config.model)) {
|
|
352
|
+
modelEntries.push({ label: '主模型', id: config.model });
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const seen = new Set();
|
|
356
|
+
const unique = [];
|
|
357
|
+
for (const e of modelEntries) {
|
|
358
|
+
if (!seen.has(e.id)) {
|
|
359
|
+
seen.add(e.id);
|
|
360
|
+
unique.push(e);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
365
|
+
const results = new Array(unique.length).fill(null);
|
|
366
|
+
let testingIdx = -1;
|
|
367
|
+
let frameIdx = 0;
|
|
368
|
+
|
|
369
|
+
function renderLines() {
|
|
370
|
+
const lines = unique.map((entry, i) => {
|
|
371
|
+
if (results[i]) {
|
|
372
|
+
return results[i].ok
|
|
373
|
+
? chalk.green(' ✓ ' + entry.label + ' (' + entry.id + ') 连接成功 ' + results[i].latency_ms + 'ms')
|
|
374
|
+
: chalk.red(' ✗ ' + entry.label + ' (' + entry.id + ') ' + results[i].error);
|
|
375
|
+
}
|
|
376
|
+
if (i === testingIdx) {
|
|
377
|
+
const frame = SPINNER_FRAMES[frameIdx % SPINNER_FRAMES.length];
|
|
378
|
+
return chalk.cyan(' ' + frame + ' ' + entry.label + ' (' + entry.id + ') 测试中...');
|
|
379
|
+
}
|
|
380
|
+
return chalk.gray(' ' + entry.label + ' (' + entry.id + ') 等待测试');
|
|
381
|
+
});
|
|
382
|
+
process.stdout.write('\x1b[' + unique.length + 'A\x1b[J');
|
|
383
|
+
process.stdout.write(lines.join('\n') + '\n');
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
console.log('');
|
|
387
|
+
console.log(chalk.bold(chalk.cyan('正在测试 Claude Code 连接...')));
|
|
388
|
+
console.log(chalk.gray(' Base URL : ') + config.baseUrl);
|
|
389
|
+
console.log(chalk.gray(' API Key : ') + maskApiKey(config.apiKey));
|
|
390
|
+
console.log('');
|
|
391
|
+
|
|
392
|
+
for (const entry of unique) {
|
|
393
|
+
console.log(chalk.gray(' ' + entry.label + ' (' + entry.id + ') 等待测试'));
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
for (let i = 0; i < unique.length; i++) {
|
|
397
|
+
testingIdx = i;
|
|
398
|
+
frameIdx = 0;
|
|
399
|
+
|
|
400
|
+
const spinnerTimer = setInterval(() => {
|
|
401
|
+
frameIdx++;
|
|
402
|
+
renderLines();
|
|
403
|
+
}, 80);
|
|
404
|
+
|
|
405
|
+
const result = await testAnthropic({
|
|
406
|
+
baseUrl: config.baseUrl,
|
|
407
|
+
apiKey: config.apiKey,
|
|
408
|
+
model: unique[i].id,
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
clearInterval(spinnerTimer);
|
|
412
|
+
results[i] = result;
|
|
413
|
+
testingIdx = -1;
|
|
414
|
+
renderLines();
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const okCount = results.filter((r) => r && r.ok).length;
|
|
418
|
+
console.log('');
|
|
419
|
+
console.log(chalk.gray(' ' + okCount + '/' + unique.length + ' 连接成功'));
|
|
420
|
+
console.log('');
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
module.exports = { run, quickSet, test };
|
|
@@ -6,6 +6,7 @@ const { API_NODES, DEFAULT_BASE_URL } = require('../models');
|
|
|
6
6
|
const { getCurrentConfig, applyConfig } = require('../claude-desktop-config');
|
|
7
7
|
const { printLogo } = require('../logo');
|
|
8
8
|
const { clearScreen } = require('../screen');
|
|
9
|
+
const { testAnthropic, testWithSpinner } = require('../test-connection');
|
|
9
10
|
|
|
10
11
|
let pendingConfig = { apiKey: null, baseUrl: null };
|
|
11
12
|
|
|
@@ -177,11 +178,46 @@ function quickSet({ key, url }) {
|
|
|
177
178
|
|
|
178
179
|
console.log('');
|
|
179
180
|
console.log(chalk.bold(chalk.cyan('Claude Desktop 配置已更新:')));
|
|
180
|
-
if (
|
|
181
|
-
console.log(chalk.green(' ✓ Base URL : ') +
|
|
181
|
+
if (toWrite.apiKey) console.log(chalk.green(' ✓ API Key : ') + maskApiKey(toWrite.apiKey));
|
|
182
|
+
if (toWrite.baseUrl) console.log(chalk.green(' ✓ Base URL : ') + toWrite.baseUrl);
|
|
182
183
|
console.log('');
|
|
183
184
|
|
|
184
185
|
return true;
|
|
185
186
|
}
|
|
186
187
|
|
|
187
|
-
|
|
188
|
+
async function test() {
|
|
189
|
+
const config = getCurrentConfig();
|
|
190
|
+
|
|
191
|
+
if (!config.apiKey) {
|
|
192
|
+
console.log(chalk.red(' ✗ 请先配置 API Key'));
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (!config.baseUrl) {
|
|
196
|
+
console.log(chalk.red(' ✗ 请先配置 Base URL'));
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const model = 'claude-sonnet-4-6';
|
|
201
|
+
|
|
202
|
+
console.log('');
|
|
203
|
+
console.log(chalk.bold(chalk.cyan('正在测试 Claude Desktop 连接...')));
|
|
204
|
+
console.log(chalk.gray(' Base URL : ') + config.baseUrl);
|
|
205
|
+
console.log(chalk.gray(' API Key : ') + maskApiKey(config.apiKey));
|
|
206
|
+
console.log('');
|
|
207
|
+
console.log(chalk.gray(' ' + model + ' 等待测试'));
|
|
208
|
+
|
|
209
|
+
const result = await testWithSpinner({
|
|
210
|
+
label: model,
|
|
211
|
+
testFn: testAnthropic,
|
|
212
|
+
args: { baseUrl: config.baseUrl, apiKey: config.apiKey, model },
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
if (result.ok) {
|
|
216
|
+
console.log(chalk.green(' ✓ ' + model + ' 连接成功 ' + result.latency_ms + 'ms'));
|
|
217
|
+
} else {
|
|
218
|
+
console.log(chalk.red(' ✗ ' + model + ' ' + result.error));
|
|
219
|
+
}
|
|
220
|
+
console.log('');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
module.exports = { run, quickSet, test };
|
package/src/tools/codex.js
CHANGED
|
@@ -12,6 +12,7 @@ const {
|
|
|
12
12
|
} = require('../codex-config');
|
|
13
13
|
const { printLogo } = require('../logo');
|
|
14
14
|
const { clearScreen } = require('../screen');
|
|
15
|
+
const { testOpenAIResponses, testWithSpinner } = require('../test-connection');
|
|
15
16
|
|
|
16
17
|
let pendingConfig = { apiKey: null, baseUrl: null, model: null, models: null };
|
|
17
18
|
|
|
@@ -312,9 +313,9 @@ async function quickSet({ key, url, model }) {
|
|
|
312
313
|
|
|
313
314
|
console.log('');
|
|
314
315
|
console.log(chalk.bold(chalk.cyan('Codex 配置已更新:')));
|
|
315
|
-
if (
|
|
316
|
-
console.log(chalk.green(' ✓ Base URL : ') +
|
|
317
|
-
console.log(chalk.green(' ✓ 模型 : ') + toWrite.model);
|
|
316
|
+
if (toWrite.apiKey) console.log(chalk.green(' ✓ API Key : ') + maskApiKey(toWrite.apiKey));
|
|
317
|
+
if (toWrite.baseUrl) console.log(chalk.green(' ✓ Base URL : ') + toWrite.baseUrl);
|
|
318
|
+
if (toWrite.model) console.log(chalk.green(' ✓ 模型 : ') + toWrite.model);
|
|
318
319
|
if (toWrite.models) {
|
|
319
320
|
console.log(chalk.gray(' (包含 ' + toWrite.models.length + ' 个 responses 模型)'));
|
|
320
321
|
}
|
|
@@ -326,4 +327,42 @@ async function quickSet({ key, url, model }) {
|
|
|
326
327
|
return true;
|
|
327
328
|
}
|
|
328
329
|
|
|
329
|
-
|
|
330
|
+
async function test() {
|
|
331
|
+
const config = getCurrentCodexConfig();
|
|
332
|
+
|
|
333
|
+
if (!config.apiKey) {
|
|
334
|
+
console.log(chalk.red(' ✗ 请先配置 API Key'));
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (!config.baseUrl) {
|
|
338
|
+
console.log(chalk.red(' ✗ 请先配置 Base URL'));
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
if (!config.model) {
|
|
342
|
+
console.log(chalk.red(' ✗ 请先配置模型'));
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
console.log('');
|
|
347
|
+
console.log(chalk.bold(chalk.cyan('正在测试 Codex 连接...')));
|
|
348
|
+
console.log(chalk.gray(' Base URL : ') + config.baseUrl);
|
|
349
|
+
console.log(chalk.gray(' 模型 : ') + config.model);
|
|
350
|
+
console.log(chalk.gray(' API Key : ') + maskApiKey(config.apiKey));
|
|
351
|
+
console.log('');
|
|
352
|
+
console.log(chalk.gray(' ' + config.model + ' 等待测试'));
|
|
353
|
+
|
|
354
|
+
const result = await testWithSpinner({
|
|
355
|
+
label: config.model,
|
|
356
|
+
testFn: testOpenAIResponses,
|
|
357
|
+
args: { baseUrl: config.baseUrl, apiKey: config.apiKey, model: config.model },
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
if (result.ok) {
|
|
361
|
+
console.log(chalk.green(' ✓ ' + config.model + ' 连接成功 ' + result.latency_ms + 'ms'));
|
|
362
|
+
} else {
|
|
363
|
+
console.log(chalk.red(' ✗ ' + config.model + ' ' + result.error));
|
|
364
|
+
}
|
|
365
|
+
console.log('');
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
module.exports = { run, quickSet, test };
|
package/src/tools/hermes.js
CHANGED
|
@@ -12,6 +12,7 @@ const {
|
|
|
12
12
|
} = require('../hermes-config');
|
|
13
13
|
const { printLogo } = require('../logo');
|
|
14
14
|
const { clearScreen } = require('../screen');
|
|
15
|
+
const { testOpenAI, testWithSpinner } = require('../test-connection');
|
|
15
16
|
|
|
16
17
|
let pendingConfig = { apiKey: null, model: null };
|
|
17
18
|
|
|
@@ -273,4 +274,38 @@ function quickSet({ key, model }) {
|
|
|
273
274
|
return true;
|
|
274
275
|
}
|
|
275
276
|
|
|
276
|
-
|
|
277
|
+
async function test() {
|
|
278
|
+
const config = getCurrentHermesConfig();
|
|
279
|
+
|
|
280
|
+
if (!config.apiKey) {
|
|
281
|
+
console.log(chalk.red(' ✗ 请先配置 API Key'));
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (!config.model) {
|
|
285
|
+
console.log(chalk.red(' ✗ 请先配置模型'));
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
console.log('');
|
|
290
|
+
console.log(chalk.bold(chalk.cyan('正在测试 Hermes 连接...')));
|
|
291
|
+
console.log(chalk.gray(' Base URL : ') + HERMES_OPENTOKEN_BASE_URL);
|
|
292
|
+
console.log(chalk.gray(' 模型 : ') + config.model);
|
|
293
|
+
console.log(chalk.gray(' API Key : ') + maskApiKey(config.apiKey));
|
|
294
|
+
console.log('');
|
|
295
|
+
console.log(chalk.gray(' ' + config.model + ' 等待测试'));
|
|
296
|
+
|
|
297
|
+
const result = await testWithSpinner({
|
|
298
|
+
label: config.model,
|
|
299
|
+
testFn: testOpenAI,
|
|
300
|
+
args: { baseUrl: HERMES_OPENTOKEN_BASE_URL, apiKey: config.apiKey, model: config.model },
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
if (result.ok) {
|
|
304
|
+
console.log(chalk.green(' ✓ ' + config.model + ' 连接成功 ' + result.latency_ms + 'ms'));
|
|
305
|
+
} else {
|
|
306
|
+
console.log(chalk.red(' ✗ ' + config.model + ' ' + result.error));
|
|
307
|
+
}
|
|
308
|
+
console.log('');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
module.exports = { run, quickSet, test };
|
package/src/tools/openclaw.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const chalk = require('chalk');
|
|
4
4
|
const inquirer = require('inquirer');
|
|
5
|
-
const { DEFAULT_BASE_URL, OPENCLAW_PROVIDER_KEY, GENERAL_DEFAULT_MODEL } = require('../models');
|
|
5
|
+
const { DEFAULT_BASE_URL, OPENCLAW_PROVIDER_KEY, OPENCLAW_OPENTOKEN_BASE_URL, GENERAL_DEFAULT_MODEL } = require('../models');
|
|
6
6
|
const { fetchModelsForBaseUrl } = require('../fetch-models');
|
|
7
7
|
const {
|
|
8
8
|
OPENCLAW_JSON_PATH,
|
|
@@ -11,6 +11,7 @@ const {
|
|
|
11
11
|
} = require('../openclaw-config');
|
|
12
12
|
const { printLogo } = require('../logo');
|
|
13
13
|
const { clearScreen } = require('../screen');
|
|
14
|
+
const { testOpenAI, testWithSpinner } = require('../test-connection');
|
|
14
15
|
|
|
15
16
|
let pendingConfig = { apiKey: null, model: null };
|
|
16
17
|
|
|
@@ -241,4 +242,40 @@ function quickSet({ key, model }) {
|
|
|
241
242
|
return true;
|
|
242
243
|
}
|
|
243
244
|
|
|
244
|
-
|
|
245
|
+
async function test() {
|
|
246
|
+
const config = getCurrentOpenclawOpentokenConfig();
|
|
247
|
+
|
|
248
|
+
if (!config.apiKey) {
|
|
249
|
+
console.log(chalk.red(' ✗ 请先配置 API Key'));
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (!config.model) {
|
|
253
|
+
console.log(chalk.red(' ✗ 请先配置模型'));
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const modelLabel = OPENCLAW_PROVIDER_KEY + '/' + config.model;
|
|
258
|
+
|
|
259
|
+
console.log('');
|
|
260
|
+
console.log(chalk.bold(chalk.cyan('正在测试 OpenClaw 连接...')));
|
|
261
|
+
console.log(chalk.gray(' Base URL : ') + OPENCLAW_OPENTOKEN_BASE_URL);
|
|
262
|
+
console.log(chalk.gray(' 模型 : ') + modelLabel);
|
|
263
|
+
console.log(chalk.gray(' API Key : ') + maskApiKey(config.apiKey));
|
|
264
|
+
console.log('');
|
|
265
|
+
console.log(chalk.gray(' ' + modelLabel + ' 等待测试'));
|
|
266
|
+
|
|
267
|
+
const result = await testWithSpinner({
|
|
268
|
+
label: modelLabel,
|
|
269
|
+
testFn: testOpenAI,
|
|
270
|
+
args: { baseUrl: OPENCLAW_OPENTOKEN_BASE_URL, apiKey: config.apiKey, model: config.model },
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
if (result.ok) {
|
|
274
|
+
console.log(chalk.green(' ✓ ' + modelLabel + ' 连接成功 ' + result.latency_ms + 'ms'));
|
|
275
|
+
} else {
|
|
276
|
+
console.log(chalk.red(' ✗ ' + modelLabel + ' ' + result.error));
|
|
277
|
+
}
|
|
278
|
+
console.log('');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
module.exports = { run, quickSet, test };
|
package/src/tools/opencode.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const chalk = require('chalk');
|
|
4
4
|
const inquirer = require('inquirer');
|
|
5
|
-
const { DEFAULT_BASE_URL, OPENCODE_PROVIDER_KEY, GENERAL_DEFAULT_MODEL } = require('../models');
|
|
5
|
+
const { DEFAULT_BASE_URL, OPENCODE_PROVIDER_KEY, OPENCODE_OPENTOKEN_BASE_URL, GENERAL_DEFAULT_MODEL } = require('../models');
|
|
6
6
|
const { fetchModelsForBaseUrl } = require('../fetch-models');
|
|
7
7
|
const {
|
|
8
8
|
OPENCODE_JSON_PATH,
|
|
@@ -11,6 +11,7 @@ const {
|
|
|
11
11
|
} = require('../opencode-config');
|
|
12
12
|
const { printLogo } = require('../logo');
|
|
13
13
|
const { clearScreen } = require('../screen');
|
|
14
|
+
const { testOpenAI, testWithSpinner } = require('../test-connection');
|
|
14
15
|
|
|
15
16
|
let pendingConfig = { apiKey: null, model: null };
|
|
16
17
|
|
|
@@ -255,4 +256,38 @@ async function quickSet({ key, model }) {
|
|
|
255
256
|
return true;
|
|
256
257
|
}
|
|
257
258
|
|
|
258
|
-
|
|
259
|
+
async function test() {
|
|
260
|
+
const config = getCurrentOpencodeOpentokenConfig();
|
|
261
|
+
|
|
262
|
+
if (!config.apiKey) {
|
|
263
|
+
console.log(chalk.red(' ✗ 请先配置 API Key'));
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (!config.model) {
|
|
267
|
+
console.log(chalk.red(' ✗ 请先配置模型'));
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
console.log('');
|
|
272
|
+
console.log(chalk.bold(chalk.cyan('正在测试 OpenCode 连接...')));
|
|
273
|
+
console.log(chalk.gray(' Base URL : ') + OPENCODE_OPENTOKEN_BASE_URL);
|
|
274
|
+
console.log(chalk.gray(' 模型 : ') + config.model);
|
|
275
|
+
console.log(chalk.gray(' API Key : ') + maskApiKey(config.apiKey));
|
|
276
|
+
console.log('');
|
|
277
|
+
console.log(chalk.gray(' ' + config.model + ' 等待测试'));
|
|
278
|
+
|
|
279
|
+
const result = await testWithSpinner({
|
|
280
|
+
label: config.model,
|
|
281
|
+
testFn: testOpenAI,
|
|
282
|
+
args: { baseUrl: OPENCODE_OPENTOKEN_BASE_URL, apiKey: config.apiKey, model: config.model },
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
if (result.ok) {
|
|
286
|
+
console.log(chalk.green(' ✓ ' + config.model + ' 连接成功 ' + result.latency_ms + 'ms'));
|
|
287
|
+
} else {
|
|
288
|
+
console.log(chalk.red(' ✗ ' + config.model + ' ' + result.error));
|
|
289
|
+
}
|
|
290
|
+
console.log('');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
module.exports = { run, quickSet, test };
|