@tokenaut/opentoken 1.3.5 → 1.4.0
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 +85 -71
- package/bin/opentoken.js +5 -4
- package/package.json +22 -10
- package/src/atomcode-config.js +94 -0
- package/src/cli.js +207 -147
- package/src/codex-config.js +44 -22
- package/src/config.js +29 -28
- package/src/fetch-models.js +228 -0
- package/src/hermes-config.js +157 -0
- package/src/logo.js +62 -0
- package/src/models.js +61 -0
- package/src/openclaw-config.js +174 -0
- package/src/opencode-config.js +166 -0
- package/src/screen.js +9 -0
- package/src/tools/atomcode.js +253 -0
- package/src/tools/claude.js +302 -0
- package/src/tools/codex.js +327 -0
- package/src/tools/hermes.js +274 -0
- package/src/tools/openclaw.js +244 -0
- package/src/tools/opencode.js +258 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const {
|
|
7
|
+
OPENCLAW_OPENTOKEN_BASE_URL,
|
|
8
|
+
OPENCLAW_PROVIDER_KEY,
|
|
9
|
+
OPENCLAW_DEFAULT_MODEL,
|
|
10
|
+
} = require('./models');
|
|
11
|
+
|
|
12
|
+
const OPENCLAW_DIR = path.join(os.homedir(), '.openclaw');
|
|
13
|
+
const OPENCLAW_JSON_PATH = path.join(OPENCLAW_DIR, 'openclaw.json');
|
|
14
|
+
|
|
15
|
+
function opentokenModelIdFromData(data) {
|
|
16
|
+
let model = '';
|
|
17
|
+
const primary =
|
|
18
|
+
data.agents &&
|
|
19
|
+
data.agents.defaults &&
|
|
20
|
+
data.agents.defaults.model &&
|
|
21
|
+
data.agents.defaults.model.primary != null
|
|
22
|
+
? String(data.agents.defaults.model.primary)
|
|
23
|
+
: '';
|
|
24
|
+
const prefix = OPENCLAW_PROVIDER_KEY + '/';
|
|
25
|
+
if (primary.startsWith(prefix)) {
|
|
26
|
+
model = primary.slice(prefix.length);
|
|
27
|
+
}
|
|
28
|
+
const p =
|
|
29
|
+
data.models &&
|
|
30
|
+
data.models.providers &&
|
|
31
|
+
data.models.providers[OPENCLAW_PROVIDER_KEY];
|
|
32
|
+
if (!model && p && Array.isArray(p.models) && p.models[0] && p.models[0].id != null) {
|
|
33
|
+
model = String(p.models[0].id);
|
|
34
|
+
}
|
|
35
|
+
return model;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function readOpenclawRoot() {
|
|
39
|
+
if (!fs.existsSync(OPENCLAW_JSON_PATH)) {
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
const raw = fs.readFileSync(OPENCLAW_JSON_PATH, 'utf-8');
|
|
43
|
+
if (!String(raw).trim()) {
|
|
44
|
+
return {};
|
|
45
|
+
}
|
|
46
|
+
const data = JSON.parse(raw);
|
|
47
|
+
if (data === null || typeof data !== 'object' || Array.isArray(data)) {
|
|
48
|
+
throw new Error('openclaw.json 根节点必须是 JSON 对象');
|
|
49
|
+
}
|
|
50
|
+
return data;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getCurrentOpenclawOpentokenConfig() {
|
|
54
|
+
try {
|
|
55
|
+
const data = readOpenclawRoot();
|
|
56
|
+
const p = data.models && data.models.providers && data.models.providers[OPENCLAW_PROVIDER_KEY];
|
|
57
|
+
const apiKey =
|
|
58
|
+
p && p.apiKey != null && String(p.apiKey).trim() !== ''
|
|
59
|
+
? String(p.apiKey)
|
|
60
|
+
: '';
|
|
61
|
+
|
|
62
|
+
const model = opentokenModelIdFromData(data);
|
|
63
|
+
return { apiKey, model };
|
|
64
|
+
} catch (_) {
|
|
65
|
+
return { apiKey: '', model: '' };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function applyOpenclawConfig(patch) {
|
|
70
|
+
const { apiKey, model } = patch || {};
|
|
71
|
+
|
|
72
|
+
let data;
|
|
73
|
+
try {
|
|
74
|
+
data = readOpenclawRoot();
|
|
75
|
+
} catch (e) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
'现有 openclaw.json 无法解析,请先备份后手动修复:' + e.message,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!data.models || typeof data.models !== 'object' || Array.isArray(data.models)) {
|
|
82
|
+
data.models = {};
|
|
83
|
+
}
|
|
84
|
+
if (data.models.mode == null || data.models.mode === '') {
|
|
85
|
+
data.models.mode = 'merge';
|
|
86
|
+
}
|
|
87
|
+
if (
|
|
88
|
+
!data.models.providers ||
|
|
89
|
+
typeof data.models.providers !== 'object' ||
|
|
90
|
+
Array.isArray(data.models.providers)
|
|
91
|
+
) {
|
|
92
|
+
data.models.providers = {};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const prev =
|
|
96
|
+
data.models.providers[OPENCLAW_PROVIDER_KEY] &&
|
|
97
|
+
typeof data.models.providers[OPENCLAW_PROVIDER_KEY] === 'object' &&
|
|
98
|
+
!Array.isArray(data.models.providers[OPENCLAW_PROVIDER_KEY])
|
|
99
|
+
? { ...data.models.providers[OPENCLAW_PROVIDER_KEY] }
|
|
100
|
+
: {};
|
|
101
|
+
|
|
102
|
+
const diskModel = opentokenModelIdFromData(data);
|
|
103
|
+
const modelId =
|
|
104
|
+
model !== undefined && model !== null && String(model).trim() !== ''
|
|
105
|
+
? String(model).trim()
|
|
106
|
+
: diskModel || OPENCLAW_DEFAULT_MODEL;
|
|
107
|
+
|
|
108
|
+
const mergedProvider = {
|
|
109
|
+
...prev,
|
|
110
|
+
baseUrl: OPENCLAW_OPENTOKEN_BASE_URL,
|
|
111
|
+
api: 'openai-completions',
|
|
112
|
+
models: [
|
|
113
|
+
{
|
|
114
|
+
id: modelId,
|
|
115
|
+
name: modelId,
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
};
|
|
119
|
+
if (apiKey !== undefined) {
|
|
120
|
+
mergedProvider.apiKey = apiKey;
|
|
121
|
+
} else if (prev.apiKey != null) {
|
|
122
|
+
mergedProvider.apiKey = prev.apiKey;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
data.models.providers[OPENCLAW_PROVIDER_KEY] = mergedProvider;
|
|
126
|
+
|
|
127
|
+
if (!data.agents || typeof data.agents !== 'object' || Array.isArray(data.agents)) {
|
|
128
|
+
data.agents = {};
|
|
129
|
+
}
|
|
130
|
+
if (
|
|
131
|
+
!data.agents.defaults ||
|
|
132
|
+
typeof data.agents.defaults !== 'object' ||
|
|
133
|
+
Array.isArray(data.agents.defaults)
|
|
134
|
+
) {
|
|
135
|
+
data.agents.defaults = {};
|
|
136
|
+
}
|
|
137
|
+
if (
|
|
138
|
+
!data.agents.defaults.model ||
|
|
139
|
+
typeof data.agents.defaults.model !== 'object' ||
|
|
140
|
+
Array.isArray(data.agents.defaults.model)
|
|
141
|
+
) {
|
|
142
|
+
data.agents.defaults.model = {};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const primaryRef = OPENCLAW_PROVIDER_KEY + '/' + modelId;
|
|
146
|
+
data.agents.defaults.model.primary = primaryRef;
|
|
147
|
+
|
|
148
|
+
if (
|
|
149
|
+
!data.agents.defaults.models ||
|
|
150
|
+
typeof data.agents.defaults.models !== 'object' ||
|
|
151
|
+
Array.isArray(data.agents.defaults.models)
|
|
152
|
+
) {
|
|
153
|
+
data.agents.defaults.models = {};
|
|
154
|
+
}
|
|
155
|
+
if (!Object.prototype.hasOwnProperty.call(data.agents.defaults.models, primaryRef)) {
|
|
156
|
+
data.agents.defaults.models[primaryRef] = {};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (!fs.existsSync(OPENCLAW_DIR)) {
|
|
160
|
+
fs.mkdirSync(OPENCLAW_DIR, { recursive: true });
|
|
161
|
+
}
|
|
162
|
+
fs.writeFileSync(
|
|
163
|
+
OPENCLAW_JSON_PATH,
|
|
164
|
+
JSON.stringify(data, null, 2),
|
|
165
|
+
'utf-8',
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
module.exports = {
|
|
170
|
+
OPENCLAW_DIR,
|
|
171
|
+
OPENCLAW_JSON_PATH,
|
|
172
|
+
getCurrentOpenclawOpentokenConfig,
|
|
173
|
+
applyOpenclawConfig,
|
|
174
|
+
};
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const {
|
|
7
|
+
OPENCODE_OPENTOKEN_BASE_URL,
|
|
8
|
+
OPENCODE_PROVIDER_KEY,
|
|
9
|
+
OPENCODE_DEFAULT_MODEL,
|
|
10
|
+
} = require('./models');
|
|
11
|
+
|
|
12
|
+
const OPENCODE_DIR =
|
|
13
|
+
process.platform === 'win32'
|
|
14
|
+
? path.join(os.homedir(), '.config', 'opencode')
|
|
15
|
+
: path.join(os.homedir(), '.config', 'opencode');
|
|
16
|
+
|
|
17
|
+
const OPENCODE_JSON_PATH = path.join(OPENCODE_DIR, 'opencode.json');
|
|
18
|
+
|
|
19
|
+
function opentokenModelIdFromData(data) {
|
|
20
|
+
const provider =
|
|
21
|
+
data.provider &&
|
|
22
|
+
data.provider[OPENCODE_PROVIDER_KEY];
|
|
23
|
+
|
|
24
|
+
if (!provider) return '';
|
|
25
|
+
|
|
26
|
+
const models = provider.models;
|
|
27
|
+
if (!models || typeof models !== 'object') return '';
|
|
28
|
+
|
|
29
|
+
const ids = Object.keys(models);
|
|
30
|
+
return ids.length > 0 ? ids[0] : '';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function readOpencodeRoot() {
|
|
34
|
+
if (!fs.existsSync(OPENCODE_JSON_PATH)) {
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
const raw = fs.readFileSync(OPENCODE_JSON_PATH, 'utf-8');
|
|
38
|
+
if (!String(raw).trim()) {
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
41
|
+
const data = JSON.parse(raw);
|
|
42
|
+
if (data === null || typeof data !== 'object' || Array.isArray(data)) {
|
|
43
|
+
throw new Error('opencode.json 根节点必须是 JSON 对象');
|
|
44
|
+
}
|
|
45
|
+
return data;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getCurrentOpencodeOpentokenConfig() {
|
|
49
|
+
try {
|
|
50
|
+
const data = readOpencodeRoot();
|
|
51
|
+
const provider =
|
|
52
|
+
data.provider &&
|
|
53
|
+
data.provider[OPENCODE_PROVIDER_KEY];
|
|
54
|
+
|
|
55
|
+
const apiKey =
|
|
56
|
+
provider &&
|
|
57
|
+
provider.options &&
|
|
58
|
+
provider.options.apiKey != null &&
|
|
59
|
+
String(provider.options.apiKey).trim() !== ''
|
|
60
|
+
? String(provider.options.apiKey)
|
|
61
|
+
: '';
|
|
62
|
+
|
|
63
|
+
const model = opentokenModelIdFromData(data);
|
|
64
|
+
return { apiKey, model };
|
|
65
|
+
} catch (_) {
|
|
66
|
+
return { apiKey: '', model: '' };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function applyOpencodeConfig(patch) {
|
|
71
|
+
const { apiKey, model, models } = patch || {};
|
|
72
|
+
|
|
73
|
+
let data;
|
|
74
|
+
try {
|
|
75
|
+
data = readOpencodeRoot();
|
|
76
|
+
} catch (e) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
'现有 opencode.json 无法解析,请先备份后手动修复:' + e.message,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!data.provider || typeof data.provider !== 'object' || Array.isArray(data.provider)) {
|
|
83
|
+
data.provider = {};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!data.$schema) {
|
|
87
|
+
data.$schema = 'https://opencode.ai/config.json';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const prev =
|
|
91
|
+
data.provider[OPENCODE_PROVIDER_KEY] &&
|
|
92
|
+
typeof data.provider[OPENCODE_PROVIDER_KEY] === 'object' &&
|
|
93
|
+
!Array.isArray(data.provider[OPENCODE_PROVIDER_KEY])
|
|
94
|
+
? { ...data.provider[OPENCODE_PROVIDER_KEY] }
|
|
95
|
+
: {};
|
|
96
|
+
|
|
97
|
+
const diskModel = opentokenModelIdFromData(data);
|
|
98
|
+
const modelId =
|
|
99
|
+
model !== undefined && model !== null && String(model).trim() !== ''
|
|
100
|
+
? String(model).trim()
|
|
101
|
+
: diskModel || OPENCODE_DEFAULT_MODEL;
|
|
102
|
+
|
|
103
|
+
const modelsConfig = {};
|
|
104
|
+
|
|
105
|
+
if (models && Array.isArray(models) && models.length > 0) {
|
|
106
|
+
models.forEach((m) => {
|
|
107
|
+
modelsConfig[m] = {
|
|
108
|
+
name: m,
|
|
109
|
+
modalities: {
|
|
110
|
+
input: ['text', 'image'],
|
|
111
|
+
output: ['text'],
|
|
112
|
+
},
|
|
113
|
+
limit: {
|
|
114
|
+
context: 120000,
|
|
115
|
+
output: 8192,
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
});
|
|
119
|
+
} else {
|
|
120
|
+
modelsConfig[modelId] = {
|
|
121
|
+
name: modelId,
|
|
122
|
+
modalities: {
|
|
123
|
+
input: ['text', 'image'],
|
|
124
|
+
output: ['text'],
|
|
125
|
+
},
|
|
126
|
+
limit: {
|
|
127
|
+
context: 120000,
|
|
128
|
+
output: 8192,
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const mergedProvider = {
|
|
134
|
+
npm: '@ai-sdk/anthropic',
|
|
135
|
+
name: 'Opentoken',
|
|
136
|
+
options: {
|
|
137
|
+
baseURL: OPENCODE_OPENTOKEN_BASE_URL,
|
|
138
|
+
},
|
|
139
|
+
models: modelsConfig,
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
if (apiKey !== undefined) {
|
|
143
|
+
mergedProvider.options.apiKey = apiKey;
|
|
144
|
+
} else if (prev.options && prev.options.apiKey != null) {
|
|
145
|
+
mergedProvider.options.apiKey = prev.options.apiKey;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
data.provider[OPENCODE_PROVIDER_KEY] = mergedProvider;
|
|
149
|
+
|
|
150
|
+
if (!fs.existsSync(OPENCODE_DIR)) {
|
|
151
|
+
fs.mkdirSync(OPENCODE_DIR, { recursive: true });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
fs.writeFileSync(
|
|
155
|
+
OPENCODE_JSON_PATH,
|
|
156
|
+
JSON.stringify(data, null, 2),
|
|
157
|
+
'utf-8',
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = {
|
|
162
|
+
OPENCODE_DIR,
|
|
163
|
+
OPENCODE_JSON_PATH,
|
|
164
|
+
getCurrentOpencodeOpentokenConfig,
|
|
165
|
+
applyOpencodeConfig,
|
|
166
|
+
};
|
package/src/screen.js
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const inquirer = require('inquirer');
|
|
5
|
+
const { DEFAULT_BASE_URL, ATOMCODE_OPENTOKEN_BASE_URL } = require('../models');
|
|
6
|
+
const { fetchModelsForBaseUrl } = require('../fetch-models');
|
|
7
|
+
const {
|
|
8
|
+
CONFIG_PATH,
|
|
9
|
+
getCurrentAtomcodeConfig,
|
|
10
|
+
applyAtomcodeConfig,
|
|
11
|
+
} = require('../atomcode-config');
|
|
12
|
+
const { printLogo } = require('../logo');
|
|
13
|
+
const { clearScreen } = require('../screen');
|
|
14
|
+
|
|
15
|
+
let pendingConfig = { apiKey: null, model: null };
|
|
16
|
+
|
|
17
|
+
function resetPending() {
|
|
18
|
+
pendingConfig = { apiKey: null, model: null };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function maskApiKey(key) {
|
|
22
|
+
if (!key || key.length < 8) return key || '';
|
|
23
|
+
return key.slice(0, 6) + '****' + key.slice(-4);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getEffectiveAtomcodeEnv() {
|
|
27
|
+
const disk = getCurrentAtomcodeConfig();
|
|
28
|
+
return {
|
|
29
|
+
apiKey: pendingConfig.apiKey !== null ? pendingConfig.apiKey : disk.apiKey,
|
|
30
|
+
model: pendingConfig.model !== null ? pendingConfig.model : disk.model,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function grayParen(text) {
|
|
35
|
+
return chalk.gray('(' + text + ')');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function buildAtomcodeMenuChoices() {
|
|
39
|
+
const ev = getEffectiveAtomcodeEnv();
|
|
40
|
+
const keyShow = ev.apiKey ? maskApiKey(ev.apiKey) : '未配置';
|
|
41
|
+
const modelShow = ev.model ? ev.model : '未配置';
|
|
42
|
+
|
|
43
|
+
return [
|
|
44
|
+
new inquirer.Separator(
|
|
45
|
+
chalk.gray(' Opentoken Base URL(固定): ' + ATOMCODE_OPENTOKEN_BASE_URL),
|
|
46
|
+
),
|
|
47
|
+
{ name: '1. 配置 API Key' + grayParen(keyShow), value: 'apiKey' },
|
|
48
|
+
{ name: '2. 配置模型' + grayParen(modelShow), value: 'model' },
|
|
49
|
+
new inquirer.Separator(),
|
|
50
|
+
{ name: '3. 应用配置并保存', value: 'apply' },
|
|
51
|
+
new inquirer.Separator(),
|
|
52
|
+
{ name: '← 返回上级菜单', value: 'back' },
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function handleConfigApiKey() {
|
|
57
|
+
const { apiKey } = await inquirer.prompt([
|
|
58
|
+
{
|
|
59
|
+
type: 'password',
|
|
60
|
+
name: 'apiKey',
|
|
61
|
+
prefix: '',
|
|
62
|
+
message: '请输入 API Key:',
|
|
63
|
+
mask: '*',
|
|
64
|
+
},
|
|
65
|
+
]);
|
|
66
|
+
|
|
67
|
+
if (apiKey && apiKey.trim()) {
|
|
68
|
+
pendingConfig.apiKey = apiKey.trim();
|
|
69
|
+
console.log(chalk.green(' ✓ API Key 已更新'));
|
|
70
|
+
} else {
|
|
71
|
+
console.log(chalk.gray(' (留空,保持不变)'));
|
|
72
|
+
}
|
|
73
|
+
console.log('');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function handleConfigModel() {
|
|
77
|
+
const { model: existing, apiKey } = getEffectiveAtomcodeEnv();
|
|
78
|
+
const current = existing || '';
|
|
79
|
+
const listBase = DEFAULT_BASE_URL;
|
|
80
|
+
|
|
81
|
+
if (!apiKey) {
|
|
82
|
+
console.log(chalk.yellow(' 请先配置 API Key,否则无法拉取模型列表。'));
|
|
83
|
+
const { modelId } = await inquirer.prompt([
|
|
84
|
+
{
|
|
85
|
+
type: 'input',
|
|
86
|
+
name: 'modelId',
|
|
87
|
+
prefix: '',
|
|
88
|
+
message: '或直接输入模型 ID:',
|
|
89
|
+
default: current,
|
|
90
|
+
validate: (v) => (v && v.trim() ? true : '请输入模型 ID'),
|
|
91
|
+
},
|
|
92
|
+
]);
|
|
93
|
+
pendingConfig.model = modelId.trim();
|
|
94
|
+
applyAtomcodeConfig({ model: pendingConfig.model });
|
|
95
|
+
console.log(chalk.green(' ✓ 模型已更新:' + pendingConfig.model));
|
|
96
|
+
console.log(
|
|
97
|
+
chalk.gray(' (已写入 ' + CONFIG_PATH + ',base_url 为 Opentoken 固定地址)'),
|
|
98
|
+
);
|
|
99
|
+
console.log('');
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const fetched = await fetchModelsForBaseUrl(listBase, apiKey);
|
|
104
|
+
|
|
105
|
+
if (fetched.ok && fetched.models.length > 0) {
|
|
106
|
+
let ids = [...fetched.models];
|
|
107
|
+
if (current && !ids.includes(current)) {
|
|
108
|
+
ids = [current, ...ids];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const modelChoices = ids.map((id) => ({
|
|
112
|
+
name: current === id ? chalk.green('► ' + id) : ' ' + id,
|
|
113
|
+
value: id,
|
|
114
|
+
}));
|
|
115
|
+
modelChoices.push(new inquirer.Separator());
|
|
116
|
+
modelChoices.push({ name: ' 手动输入其他模型 ID…', value: '__manual__' });
|
|
117
|
+
|
|
118
|
+
const defaultValue = current && ids.includes(current) ? current : ids[0];
|
|
119
|
+
|
|
120
|
+
const { model } = await inquirer.prompt([
|
|
121
|
+
{
|
|
122
|
+
type: 'list',
|
|
123
|
+
name: 'model',
|
|
124
|
+
prefix: '',
|
|
125
|
+
message: '请选择模型:',
|
|
126
|
+
choices: modelChoices,
|
|
127
|
+
default: defaultValue,
|
|
128
|
+
pageSize: 15,
|
|
129
|
+
},
|
|
130
|
+
]);
|
|
131
|
+
|
|
132
|
+
if (model === '__manual__') {
|
|
133
|
+
const { modelId } = await inquirer.prompt([
|
|
134
|
+
{
|
|
135
|
+
type: 'input',
|
|
136
|
+
name: 'modelId',
|
|
137
|
+
prefix: '',
|
|
138
|
+
message: '请输入模型 ID:',
|
|
139
|
+
default: current || '',
|
|
140
|
+
validate: (v) => (v && v.trim() ? true : '请输入模型 ID'),
|
|
141
|
+
},
|
|
142
|
+
]);
|
|
143
|
+
pendingConfig.model = modelId.trim();
|
|
144
|
+
} else {
|
|
145
|
+
pendingConfig.model = model;
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
console.log(chalk.yellow(' 无法拉取模型列表:' + (fetched.error || '未知错误')));
|
|
149
|
+
console.log(chalk.gray(' 请检查网络或 API Key 后重试,或直接输入模型 ID。'));
|
|
150
|
+
const { modelId } = await inquirer.prompt([
|
|
151
|
+
{
|
|
152
|
+
type: 'input',
|
|
153
|
+
name: 'modelId',
|
|
154
|
+
prefix: '',
|
|
155
|
+
message: '请输入模型 ID:',
|
|
156
|
+
default: current,
|
|
157
|
+
validate: (v) => (v && v.trim() ? true : '请输入模型 ID'),
|
|
158
|
+
},
|
|
159
|
+
]);
|
|
160
|
+
pendingConfig.model = modelId.trim();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
applyAtomcodeConfig({ model: pendingConfig.model });
|
|
164
|
+
console.log(chalk.green(' ✓ 模型已更新:' + pendingConfig.model));
|
|
165
|
+
console.log(
|
|
166
|
+
chalk.gray(' (已写入 ' + CONFIG_PATH + ',base_url 为 Opentoken 固定地址)'),
|
|
167
|
+
);
|
|
168
|
+
console.log('');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function doApplyConfig() {
|
|
172
|
+
const toWrite = {};
|
|
173
|
+
if (pendingConfig.apiKey !== null) toWrite.apiKey = pendingConfig.apiKey;
|
|
174
|
+
if (pendingConfig.model !== null) toWrite.model = pendingConfig.model;
|
|
175
|
+
|
|
176
|
+
console.log('');
|
|
177
|
+
if (Object.keys(toWrite).length > 0) {
|
|
178
|
+
applyAtomcodeConfig(toWrite);
|
|
179
|
+
console.log(chalk.green(' ✓ 配置已成功写入'));
|
|
180
|
+
console.log(chalk.gray(' ' + CONFIG_PATH));
|
|
181
|
+
} else {
|
|
182
|
+
applyAtomcodeConfig({});
|
|
183
|
+
console.log(
|
|
184
|
+
chalk.gray(
|
|
185
|
+
' (无 Key / 模型变更;已确保 default_provider 与 Opentoken 固定 base_url)',
|
|
186
|
+
),
|
|
187
|
+
);
|
|
188
|
+
console.log(chalk.gray(' ' + CONFIG_PATH));
|
|
189
|
+
}
|
|
190
|
+
console.log('');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function paintScreen() {
|
|
194
|
+
clearScreen();
|
|
195
|
+
printLogo();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function run() {
|
|
199
|
+
resetPending();
|
|
200
|
+
|
|
201
|
+
while (true) {
|
|
202
|
+
paintScreen();
|
|
203
|
+
|
|
204
|
+
const { action } = await inquirer.prompt([
|
|
205
|
+
{
|
|
206
|
+
type: 'list',
|
|
207
|
+
name: 'action',
|
|
208
|
+
prefix: '',
|
|
209
|
+
message: 'Atomcode 配置(~/.atomcode/config.toml):',
|
|
210
|
+
choices: buildAtomcodeMenuChoices(),
|
|
211
|
+
pageSize: 12,
|
|
212
|
+
},
|
|
213
|
+
]);
|
|
214
|
+
|
|
215
|
+
switch (action) {
|
|
216
|
+
case 'apiKey':
|
|
217
|
+
paintScreen();
|
|
218
|
+
await handleConfigApiKey();
|
|
219
|
+
break;
|
|
220
|
+
case 'model':
|
|
221
|
+
paintScreen();
|
|
222
|
+
await handleConfigModel();
|
|
223
|
+
break;
|
|
224
|
+
case 'apply':
|
|
225
|
+
doApplyConfig();
|
|
226
|
+
return;
|
|
227
|
+
case 'back':
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function quickSet({ key, model }) {
|
|
234
|
+
const toWrite = {};
|
|
235
|
+
if (key) toWrite.apiKey = key;
|
|
236
|
+
if (model) toWrite.model = model;
|
|
237
|
+
|
|
238
|
+
if (Object.keys(toWrite).length === 0) return false;
|
|
239
|
+
|
|
240
|
+
applyAtomcodeConfig(toWrite);
|
|
241
|
+
|
|
242
|
+
console.log('');
|
|
243
|
+
console.log(chalk.bold(chalk.cyan('Atomcode 配置已更新:')));
|
|
244
|
+
if (key) console.log(chalk.green(' ✓ API Key : ') + maskApiKey(key));
|
|
245
|
+
if (model) console.log(chalk.green(' ✓ 模型 : ') + model);
|
|
246
|
+
console.log(chalk.green(' ✓ Base URL : ') + ATOMCODE_OPENTOKEN_BASE_URL + chalk.gray('(固定)'));
|
|
247
|
+
console.log(chalk.gray(' ' + CONFIG_PATH));
|
|
248
|
+
console.log('');
|
|
249
|
+
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
module.exports = { run, quickSet };
|