@tokenaut/opentoken 1.4.8 → 1.4.10

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 CHANGED
@@ -24,7 +24,7 @@ npm install -g @tokenaut/opentoken
24
24
  | 应用 | 子命令(别名) | 配置文件 |
25
25
  |------|----------------|----------|
26
26
  | **Claude Code** | `cc`、`claude` | `~/.claude/settings.json`(环境变量:`ANTHROPIC_AUTH_TOKEN`、`ANTHROPIC_BASE_URL`、`ANTHROPIC_MODEL`) |
27
- | **Claude Desktop** | `claude_desktop`、`cdp` | `~/Library/Application Support/Claude-3p/configLibrary/*.json`(`inferenceGatewayApiKey`、`inferenceGatewayBaseUrl`) |
27
+ | **Claude Desktop** | `claude_desktop`、`cdt` | `~/Library/Application Support/Claude-3p/configLibrary/*.json`(`inferenceGatewayApiKey`、`inferenceGatewayBaseUrl`) |
28
28
  | **Atomcode** | `atomcode`、`ac` | `~/.atomcode/config.toml` |
29
29
  | **OpenClaw** | `openclaw`、`oc` | `~/.openclaw/openclaw.json`(合并写入 Opentoken:`models.providers.opentoken`、`agents.defaults.model.primary` 等) |
30
30
  | **Hermes** | `hermes`、`hm` | `~/.hermes/config.yaml`(`model.provider`、`model.base_url`、`model.default`)与 `~/.hermes/.env`(`OPENTOKEN_API_KEY`) |
@@ -69,15 +69,15 @@ opentoken cc -k sk-xxx -u https://gw.opentoken.io -m claude-opus-4-5
69
69
 
70
70
  ```bash
71
71
  opentoken claude_desktop --key <api_key>
72
- opentoken cdp --url <base_url>
73
- opentoken cdp -k <api_key> -u <base_url>
72
+ opentoken cdt --url <base_url>
73
+ opentoken cdt -k <api_key> -u <base_url>
74
74
  ```
75
75
 
76
76
  示例:
77
77
 
78
78
  ```bash
79
- opentoken cdp -k sk-xxx
80
- opentoken cdp -k sk-xxx -u https://gw.opentoken.io
79
+ opentoken cdt -k sk-xxx
80
+ opentoken cdt -k sk-xxx -u https://gw.opentoken.io
81
81
  ```
82
82
 
83
83
  ### Atomcode
@@ -146,6 +146,39 @@ 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 cdt test
156
+ opentoken ac test
157
+ opentoken hm test
158
+ opentoken oc test
159
+ opentoken od test
160
+ opentoken cx test
161
+ ```
162
+
163
+ ---
164
+
165
+ ## 查看可用模型
166
+
167
+ 查看默认节点所有可用模型(自动使用已配置的 API Key):
168
+
169
+ ```bash
170
+ opentoken models
171
+ ```
172
+
173
+ 使用指定 Key 查询:
174
+
175
+ ```bash
176
+ opentoken models -k <api_key>
177
+ opentoken models --key <api_key>
178
+ ```
179
+
180
+ ---
181
+
149
182
  ## 环境
150
183
 
151
184
  Node.js >= 14 · MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenaut/opentoken",
3
- "version": "1.4.8",
3
+ "version": "1.4.10",
4
4
  "description": "OpenToken 一键接入 AI 模型 · https://docs.opentoken.io/",
5
5
  "main": "src/cli.js",
6
6
  "bin": {
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';
@@ -38,7 +40,7 @@ function isCodexCommand(tool) {
38
40
  }
39
41
 
40
42
  function isClaudeDesktopCommand(tool) {
41
- return tool === 'claude_desktop' || tool === 'cdp';
43
+ return tool === 'claude_desktop' || tool === 'cdt';
42
44
  }
43
45
 
44
46
  function printVersion() {
@@ -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 进入主菜单');
@@ -92,17 +96,26 @@ function printHelp() {
92
96
  console.log(' opentoken cx -k <k> -u <u> -m <m> 一键设置 URL 与模型');
93
97
  console.log('');
94
98
  console.log(' opentoken claude_desktop 进入 Claude Desktop 配置(configLibrary/*.json)');
95
- console.log(' opentoken cdp 同上(缩写)');
96
- console.log(' opentoken cdp --key <apikey> 设置 API Key(inferenceGatewayApiKey)');
97
- console.log(' opentoken cdp --url <baseurl> 设置 Base URL(inferenceGatewayBaseUrl)');
98
- console.log(' opentoken cdp -k <k> -u <u> 一键设置 Key 与 URL');
99
+ console.log(' opentoken cdt 同上(缩写)');
100
+ console.log(' opentoken cdt --key <apikey> 设置 API Key(inferenceGatewayApiKey)');
101
+ console.log(' opentoken cdt --url <baseurl> 设置 Base URL(inferenceGatewayBaseUrl)');
102
+ console.log(' opentoken cdt -k <k> -u <u> 一键设置 Key 与 URL');
103
+ console.log('');
104
+ console.log(chalk.bold('连接测试:'));
105
+ console.log(' opentoken cc test 测试 Claude Code 连接');
106
+ console.log(' opentoken cdt test 测试 Claude Desktop 连接');
107
+ console.log(' opentoken ac test 测试 Atomcode 连接');
108
+ console.log(' opentoken hm test 测试 Hermes 连接');
109
+ console.log(' opentoken oc test 测试 OpenClaw 连接');
110
+ console.log(' opentoken od test 测试 OpenCode 连接');
111
+ console.log(' opentoken cx test 测试 Codex 连接');
99
112
  console.log('');
100
113
  console.log(chalk.bold('快捷别名(--key / -k,--url / -u,--model / -m)'));
101
114
  console.log('');
102
115
  console.log(chalk.bold('示例:'));
103
116
  console.log(' opentoken cc -k sk-xxx');
104
117
  console.log(` opentoken cc -k sk-xxx -u ${DEFAULT_BASE_URL} -m claude-opus-4-5`);
105
- console.log(` opentoken cdp -k sk-xxx -u ${DEFAULT_BASE_URL}`);
118
+ console.log(` opentoken cdt -k sk-xxx -u ${DEFAULT_BASE_URL}`);
106
119
  console.log('');
107
120
  console.log(chalk.bold('主菜单:'));
108
121
  console.log(' 1. 配置 Claude Code');
@@ -120,6 +133,7 @@ function parseArgs(argv) {
120
133
  const args = argv.slice(2);
121
134
  const result = {
122
135
  tool: null,
136
+ subcommand: null,
123
137
  key: null,
124
138
  url: null,
125
139
  model: null,
@@ -134,6 +148,11 @@ function parseArgs(argv) {
134
148
  i = 1;
135
149
  }
136
150
 
151
+ if (args[i] && !args[i].startsWith('-')) {
152
+ result.subcommand = args[i].toLowerCase();
153
+ i++;
154
+ }
155
+
137
156
  for (; i < args.length; i++) {
138
157
  const a = args[i];
139
158
  const next = args[i + 1];
@@ -228,6 +247,47 @@ async function run() {
228
247
  return;
229
248
  }
230
249
 
250
+ if (args.tool === 'models') {
251
+ const apiKey = args.key || getCurrentConfig().apiKey;
252
+ const maskKey = (k) => {
253
+ if (!k || k.length < 8) return k || '';
254
+ return k.slice(0, 6) + '****' + k.slice(-4);
255
+ };
256
+ console.log('');
257
+ console.log(chalk.bold(chalk.cyan('可用模型列表')) + chalk.gray('(' + DEFAULT_BASE_URL + ')'));
258
+ console.log('');
259
+ if (!apiKey) {
260
+ console.log(chalk.red(' ✗ 未配置 API Key,请使用 opentoken models -k <apikey> 或先配置 Key'));
261
+ console.log('');
262
+ return;
263
+ }
264
+ const result = await fetchModelsForBaseUrl(DEFAULT_BASE_URL, apiKey);
265
+ if (result.ok && result.models.length > 0) {
266
+ result.models.forEach((id) => console.log(' ' + id));
267
+ console.log('');
268
+ console.log(chalk.gray('共 ' + result.models.length + ' 个模型'));
269
+ } else {
270
+ console.log(chalk.red(' ✗ 获取失败:' + (result.error || '未知错误')));
271
+ }
272
+ console.log(chalk.gray('API Key : ' + maskKey(apiKey)));
273
+ console.log('');
274
+ return;
275
+ }
276
+
277
+ if (args.subcommand === 'test') {
278
+ clearScreen();
279
+ printLogo();
280
+ if (isCcCommand(args.tool)) { await claude.test(); return; }
281
+ if (isClaudeDesktopCommand(args.tool)) { await claudeDesktop.test(); return; }
282
+ if (isAtomcodeCommand(args.tool)) { await atomcode.test(); return; }
283
+ if (isHermesCommand(args.tool)) { await hermes.test(); return; }
284
+ if (isOpenclawCommand(args.tool)) { await openclaw.test(); return; }
285
+ if (isOpencodeCommand(args.tool)) { await opencode.test(); return; }
286
+ if (isCodexCommand(args.tool)) { await codex.test(); return; }
287
+ console.log(chalk.red('未知工具:' + args.tool));
288
+ return;
289
+ }
290
+
231
291
  if (isCcCommand(args.tool) && (args.key || args.url || args.model)) {
232
292
  clearScreen();
233
293
  printLogo();
@@ -0,0 +1,146 @@
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
+ module.exports = { testAnthropic, testOpenAI, testOpenAIResponses };
@@ -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 } = require('../test-connection');
14
15
 
15
16
  let pendingConfig = { apiKey: null, model: null };
16
17
 
@@ -252,4 +253,37 @@ function quickSet({ key, model }) {
252
253
  return true;
253
254
  }
254
255
 
255
- module.exports = { run, quickSet };
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
+
275
+ const result = await testOpenAI({
276
+ baseUrl: ATOMCODE_OPENTOKEN_BASE_URL,
277
+ apiKey: config.apiKey,
278
+ model: config.model,
279
+ });
280
+
281
+ if (result.ok) {
282
+ console.log(chalk.green(' ✓ 连接成功!延迟 ' + result.latency_ms + 'ms'));
283
+ } else {
284
+ console.log(chalk.red(' ✗ 连接失败:' + result.error));
285
+ }
286
+ console.log('');
287
+ }
288
+
289
+ module.exports = { run, quickSet, test };
@@ -7,6 +7,7 @@ const { fetchModelsForBaseUrl } = require('../fetch-models');
7
7
  const { getCurrentConfig, applyConfig } = require('../config');
8
8
  const { printLogo } = require('../logo');
9
9
  const { clearScreen } = require('../screen');
10
+ const { testAnthropic } = require('../test-connection');
10
11
 
11
12
  let pendingConfig = { apiKey: null, baseUrl: null, model: null };
12
13
 
@@ -294,12 +295,49 @@ function quickSet({ key, url, model }) {
294
295
 
295
296
  console.log('');
296
297
  console.log(chalk.bold(chalk.cyan('Claude Code 配置已更新:')));
297
- if (key) console.log(chalk.green(' ✓ API Key : ') + maskApiKey(key));
298
- console.log(chalk.green(' ✓ Base URL : ') + (toWrite.baseUrl || url));
299
- console.log(chalk.green(' ✓ 模型 : ') + (toWrite.model || model));
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
- module.exports = { run, quickSet };
306
+ async function test() {
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
+ if (!config.model) {
318
+ console.log(chalk.red(' ✗ 请先配置模型'));
319
+ return;
320
+ }
321
+
322
+ console.log('');
323
+ console.log(chalk.bold(chalk.cyan('正在测试 Claude Code 连接...')));
324
+ console.log(chalk.gray(' Base URL : ') + config.baseUrl);
325
+ console.log(chalk.gray(' 模型 : ') + config.model);
326
+ console.log(chalk.gray(' API Key : ') + maskApiKey(config.apiKey));
327
+ console.log('');
328
+
329
+ const result = await testAnthropic({
330
+ baseUrl: config.baseUrl,
331
+ apiKey: config.apiKey,
332
+ model: config.model,
333
+ });
334
+
335
+ if (result.ok) {
336
+ console.log(chalk.green(' ✓ 连接成功!延迟 ' + result.latency_ms + 'ms'));
337
+ } else {
338
+ console.log(chalk.red(' ✗ 连接失败:' + result.error));
339
+ }
340
+ console.log('');
341
+ }
342
+
343
+ 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 } = require('../test-connection');
9
10
 
10
11
  let pendingConfig = { apiKey: null, baseUrl: null };
11
12
 
@@ -177,11 +178,43 @@ function quickSet({ key, url }) {
177
178
 
178
179
  console.log('');
179
180
  console.log(chalk.bold(chalk.cyan('Claude Desktop 配置已更新:')));
180
- if (key) console.log(chalk.green(' ✓ API Key : ') + maskApiKey(key));
181
- console.log(chalk.green(' ✓ Base URL : ') + (toWrite.baseUrl || 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
- module.exports = { run, quickSet };
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
+ console.log('');
201
+ console.log(chalk.bold(chalk.cyan('正在测试 Claude Desktop 连接...')));
202
+ console.log(chalk.gray(' Base URL : ') + config.baseUrl);
203
+ console.log(chalk.gray(' API Key : ') + maskApiKey(config.apiKey));
204
+ console.log('');
205
+
206
+ const result = await testAnthropic({
207
+ baseUrl: config.baseUrl,
208
+ apiKey: config.apiKey,
209
+ model: 'claude-sonnet-4-6',
210
+ });
211
+
212
+ if (result.ok) {
213
+ console.log(chalk.green(' ✓ 连接成功!延迟 ' + result.latency_ms + 'ms'));
214
+ } else {
215
+ console.log(chalk.red(' ✗ 连接失败:' + result.error));
216
+ }
217
+ console.log('');
218
+ }
219
+
220
+ module.exports = { run, quickSet, test };
@@ -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 } = 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 (key) console.log(chalk.green(' ✓ API Key : ') + maskApiKey(key));
316
- console.log(chalk.green(' ✓ Base URL : ') + (toWrite.baseUrl || 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,41 @@ async function quickSet({ key, url, model }) {
326
327
  return true;
327
328
  }
328
329
 
329
- module.exports = { run, quickSet };
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
+
353
+ const result = await testOpenAIResponses({
354
+ baseUrl: config.baseUrl,
355
+ apiKey: config.apiKey,
356
+ model: config.model,
357
+ });
358
+
359
+ if (result.ok) {
360
+ console.log(chalk.green(' ✓ 连接成功!延迟 ' + result.latency_ms + 'ms'));
361
+ } else {
362
+ console.log(chalk.red(' ✗ 连接失败:' + result.error));
363
+ }
364
+ console.log('');
365
+ }
366
+
367
+ module.exports = { run, quickSet, test };
@@ -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 } = require('../test-connection');
15
16
 
16
17
  let pendingConfig = { apiKey: null, model: null };
17
18
 
@@ -273,4 +274,37 @@ function quickSet({ key, model }) {
273
274
  return true;
274
275
  }
275
276
 
276
- module.exports = { run, quickSet };
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
+
296
+ const result = await testOpenAI({
297
+ baseUrl: HERMES_OPENTOKEN_BASE_URL,
298
+ apiKey: config.apiKey,
299
+ model: config.model,
300
+ });
301
+
302
+ if (result.ok) {
303
+ console.log(chalk.green(' ✓ 连接成功!延迟 ' + result.latency_ms + 'ms'));
304
+ } else {
305
+ console.log(chalk.red(' ✗ 连接失败:' + result.error));
306
+ }
307
+ console.log('');
308
+ }
309
+
310
+ module.exports = { run, quickSet, test };
@@ -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 } = require('../test-connection');
14
15
 
15
16
  let pendingConfig = { apiKey: null, model: null };
16
17
 
@@ -241,4 +242,37 @@ function quickSet({ key, model }) {
241
242
  return true;
242
243
  }
243
244
 
244
- module.exports = { run, quickSet };
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
+ console.log('');
258
+ console.log(chalk.bold(chalk.cyan('正在测试 OpenClaw 连接...')));
259
+ console.log(chalk.gray(' Base URL : ') + OPENCLAW_OPENTOKEN_BASE_URL);
260
+ console.log(chalk.gray(' 模型 : ') + OPENCLAW_PROVIDER_KEY + '/' + config.model);
261
+ console.log(chalk.gray(' API Key : ') + maskApiKey(config.apiKey));
262
+ console.log('');
263
+
264
+ const result = await testOpenAI({
265
+ baseUrl: OPENCLAW_OPENTOKEN_BASE_URL,
266
+ apiKey: config.apiKey,
267
+ model: config.model,
268
+ });
269
+
270
+ if (result.ok) {
271
+ console.log(chalk.green(' ✓ 连接成功!延迟 ' + result.latency_ms + 'ms'));
272
+ } else {
273
+ console.log(chalk.red(' ✗ 连接失败:' + result.error));
274
+ }
275
+ console.log('');
276
+ }
277
+
278
+ module.exports = { run, quickSet, test };
@@ -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 } = require('../test-connection');
14
15
 
15
16
  let pendingConfig = { apiKey: null, model: null };
16
17
 
@@ -255,4 +256,37 @@ async function quickSet({ key, model }) {
255
256
  return true;
256
257
  }
257
258
 
258
- module.exports = { run, quickSet };
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
+
278
+ const result = await testOpenAI({
279
+ baseUrl: OPENCODE_OPENTOKEN_BASE_URL,
280
+ apiKey: config.apiKey,
281
+ model: config.model,
282
+ });
283
+
284
+ if (result.ok) {
285
+ console.log(chalk.green(' ✓ 连接成功!延迟 ' + result.latency_ms + 'ms'));
286
+ } else {
287
+ console.log(chalk.red(' ✗ 连接失败:' + result.error));
288
+ }
289
+ console.log('');
290
+ }
291
+
292
+ module.exports = { run, quickSet, test };