@semalt-ai/code 1.3.1 → 1.4.1
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 +23 -1
- package/index.js +140 -37
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -67,6 +67,7 @@ Example config:
|
|
|
67
67
|
"default_model": "default",
|
|
68
68
|
"temperature": 0.7,
|
|
69
69
|
"max_tokens": 4096,
|
|
70
|
+
"request_timeout_ms": 900000,
|
|
70
71
|
"stream": true
|
|
71
72
|
}
|
|
72
73
|
```
|
|
@@ -95,7 +96,10 @@ semalt-code [command] [options]
|
|
|
95
96
|
Runs a shell command with approval prompts.
|
|
96
97
|
|
|
97
98
|
- `semalt-code models`
|
|
98
|
-
Lists
|
|
99
|
+
Lists all saved model profiles.
|
|
100
|
+
|
|
101
|
+
- `semalt-code models add`
|
|
102
|
+
Opens an interactive flow to add an API base URL, API key, and model ID as a reusable model profile.
|
|
99
103
|
|
|
100
104
|
- `semalt-code init`
|
|
101
105
|
Creates or updates the local config file.
|
|
@@ -134,7 +138,9 @@ Available interactive commands:
|
|
|
134
138
|
|
|
135
139
|
- `/help`
|
|
136
140
|
- `/file <path>`
|
|
141
|
+
- `/model`
|
|
137
142
|
- `/model <name>`
|
|
143
|
+
- `/models`
|
|
138
144
|
- `/clear`
|
|
139
145
|
- `/compact`
|
|
140
146
|
- `/cost`
|
|
@@ -205,6 +211,22 @@ semalt-code shell -a "npm test"
|
|
|
205
211
|
semalt-code models
|
|
206
212
|
```
|
|
207
213
|
|
|
214
|
+
### Add a saved model profile
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
semalt-code models add
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
The CLI will ask for:
|
|
221
|
+
|
|
222
|
+
- API Base URL
|
|
223
|
+
- API Key
|
|
224
|
+
- Model ID
|
|
225
|
+
|
|
226
|
+
Each saved profile is appended to the profile list.
|
|
227
|
+
|
|
228
|
+
Saved profiles can then be selected inside chat mode with `/model` or `/models`.
|
|
229
|
+
|
|
208
230
|
### Show the current version
|
|
209
231
|
|
|
210
232
|
```bash
|
package/index.js
CHANGED
|
@@ -12,6 +12,8 @@ const { URL } = require('url');
|
|
|
12
12
|
|
|
13
13
|
const PACKAGE_JSON = require('./package.json');
|
|
14
14
|
|
|
15
|
+
const DEFAULT_API_TIMEOUT_MS = 15 * 60 * 1000;
|
|
16
|
+
|
|
15
17
|
// ── Config ────────────────────────────────────────────────────────────────────
|
|
16
18
|
|
|
17
19
|
const DEFAULT_CONFIG = {
|
|
@@ -20,24 +22,45 @@ const DEFAULT_CONFIG = {
|
|
|
20
22
|
default_model: 'default',
|
|
21
23
|
temperature: 0.7,
|
|
22
24
|
max_tokens: 4096,
|
|
25
|
+
request_timeout_ms: DEFAULT_API_TIMEOUT_MS,
|
|
23
26
|
stream: true,
|
|
27
|
+
models: [],
|
|
24
28
|
};
|
|
25
29
|
|
|
26
30
|
const CONFIG_PATH = path.join(os.homedir(), '.semalt-ai', 'config.json');
|
|
27
31
|
|
|
32
|
+
function normalizeConfig(cfg = {}) {
|
|
33
|
+
const merged = { ...DEFAULT_CONFIG, ...cfg };
|
|
34
|
+
merged.models = Array.isArray(cfg.models)
|
|
35
|
+
? cfg.models
|
|
36
|
+
.filter((entry) => entry &&
|
|
37
|
+
typeof entry.api_base === 'string' &&
|
|
38
|
+
typeof entry.api_key === 'string' &&
|
|
39
|
+
typeof entry.model === 'string' &&
|
|
40
|
+
entry.api_base.trim() &&
|
|
41
|
+
entry.model.trim())
|
|
42
|
+
.map((entry) => ({
|
|
43
|
+
api_base: entry.api_base.trim(),
|
|
44
|
+
api_key: entry.api_key,
|
|
45
|
+
model: entry.model.trim(),
|
|
46
|
+
}))
|
|
47
|
+
: [];
|
|
48
|
+
return merged;
|
|
49
|
+
}
|
|
50
|
+
|
|
28
51
|
function loadConfig() {
|
|
29
52
|
if (fs.existsSync(CONFIG_PATH)) {
|
|
30
53
|
try {
|
|
31
54
|
const data = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
|
|
32
|
-
return
|
|
55
|
+
return normalizeConfig(data);
|
|
33
56
|
} catch {}
|
|
34
57
|
}
|
|
35
|
-
return
|
|
58
|
+
return normalizeConfig();
|
|
36
59
|
}
|
|
37
60
|
|
|
38
61
|
function saveConfig(cfg) {
|
|
39
62
|
fs.mkdirSync(path.dirname(CONFIG_PATH), { recursive: true });
|
|
40
|
-
fs.writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2));
|
|
63
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(normalizeConfig(cfg), null, 2));
|
|
41
64
|
}
|
|
42
65
|
|
|
43
66
|
let config = loadConfig();
|
|
@@ -424,6 +447,52 @@ function apiUrl(urlPath) {
|
|
|
424
447
|
return `${normalizedBase}${normalizedPath}`;
|
|
425
448
|
}
|
|
426
449
|
|
|
450
|
+
function describeModelProfile(profile) {
|
|
451
|
+
return `${profile.model} @ ${profile.api_base}`;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function setActiveModelProfile(profile) {
|
|
455
|
+
config.api_base = profile.api_base;
|
|
456
|
+
config.api_key = profile.api_key;
|
|
457
|
+
config.default_model = profile.model;
|
|
458
|
+
saveConfig(config);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function chooseSavedModelProfile(rl, currentModel, cwd, onDone) {
|
|
462
|
+
if (!config.models.length) {
|
|
463
|
+
console.log(` ${FG_RED}✗${RST} ${FG_GRAY}No saved model profiles. Use semalt-code models add first.${RST}`);
|
|
464
|
+
onDone(currentModel);
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
console.log();
|
|
469
|
+
console.log(` ${FG_TEAL}${BOLD}◆ Saved Models${RST}`);
|
|
470
|
+
console.log(` ${FG_DARK}${'─'.repeat(40)}${RST}`);
|
|
471
|
+
config.models.forEach((profile, index) => {
|
|
472
|
+
const active = profile.api_base === config.api_base &&
|
|
473
|
+
profile.api_key === config.api_key &&
|
|
474
|
+
profile.model === currentModel;
|
|
475
|
+
const marker = active ? `${FG_GREEN}●${RST}` : `${FG_DARK}○${RST}`;
|
|
476
|
+
console.log(` ${marker} ${FG_CYAN}${index + 1}.${RST} ${describeModelProfile(profile)}`);
|
|
477
|
+
});
|
|
478
|
+
console.log();
|
|
479
|
+
|
|
480
|
+
rl.question(` ${FG_TEAL}${BOLD}Select model>${RST} `, (answer) => {
|
|
481
|
+
const selected = Number((answer || '').trim());
|
|
482
|
+
if (!Number.isInteger(selected) || selected < 1 || selected > config.models.length) {
|
|
483
|
+
console.log(` ${FG_RED}✗${RST} ${FG_GRAY}Invalid selection${RST}`);
|
|
484
|
+
onDone(currentModel);
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const profile = config.models[selected - 1];
|
|
489
|
+
setActiveModelProfile(profile);
|
|
490
|
+
console.log(` ${FG_GREEN}✓${RST} ${FG_GRAY}Model profile → ${describeModelProfile(profile)}${RST}`);
|
|
491
|
+
printStatusBar(profile.model, cwd);
|
|
492
|
+
onDone(profile.model);
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
427
496
|
function estimateTokens(text) {
|
|
428
497
|
return Math.floor((text || '').length / 4);
|
|
429
498
|
}
|
|
@@ -472,6 +541,7 @@ async function chatStream(messages, { model, temperature, maxTokens } = {}) {
|
|
|
472
541
|
try {
|
|
473
542
|
res = await httpRequest(apiUrl('/v1/chat/completions'), {
|
|
474
543
|
method: 'POST',
|
|
544
|
+
timeout: config.request_timeout_ms,
|
|
475
545
|
headers: {
|
|
476
546
|
'Content-Type': 'application/json',
|
|
477
547
|
'Authorization': `Bearer ${config.api_key}`,
|
|
@@ -573,6 +643,7 @@ async function chatSync(messages, { model } = {}) {
|
|
|
573
643
|
try {
|
|
574
644
|
res = await httpRequest(apiUrl('/v1/chat/completions'), {
|
|
575
645
|
method: 'POST',
|
|
646
|
+
timeout: config.request_timeout_ms,
|
|
576
647
|
headers: {
|
|
577
648
|
'Content-Type': 'application/json',
|
|
578
649
|
'Authorization': `Bearer ${config.api_key}`,
|
|
@@ -831,7 +902,9 @@ async function cmdChat(opts) {
|
|
|
831
902
|
console.log(`
|
|
832
903
|
${FG_BLUE}${BOLD}Commands:${RST}
|
|
833
904
|
${FG_CYAN}/file <path>${RST} ${FG_GRAY}Load file or dir into context${RST}
|
|
834
|
-
${FG_CYAN}/model
|
|
905
|
+
${FG_CYAN}/model${RST} ${FG_GRAY}Choose saved model profile${RST}
|
|
906
|
+
${FG_CYAN}/model <name>${RST} ${FG_GRAY}Switch model manually${RST}
|
|
907
|
+
${FG_CYAN}/models${RST} ${FG_GRAY}Choose saved model profile${RST}
|
|
835
908
|
${FG_CYAN}/clear${RST} ${FG_GRAY}Clear conversation${RST}
|
|
836
909
|
${FG_CYAN}/compact${RST} ${FG_GRAY}Show token usage${RST}
|
|
837
910
|
${FG_CYAN}/shell <cmd>${RST} ${FG_GRAY}Run shell command directly${RST}
|
|
@@ -852,6 +925,14 @@ async function cmdChat(opts) {
|
|
|
852
925
|
return prompt();
|
|
853
926
|
}
|
|
854
927
|
|
|
928
|
+
if (text === '/model' || text === '/models') {
|
|
929
|
+
chooseSavedModelProfile(rl, currentModel, cwd, (nextModel) => {
|
|
930
|
+
currentModel = nextModel;
|
|
931
|
+
prompt();
|
|
932
|
+
});
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
|
|
855
936
|
if (text.startsWith('/model ')) {
|
|
856
937
|
currentModel = text.slice(7).trim();
|
|
857
938
|
console.log(` ${FG_GREEN}✓${RST} ${FG_GRAY}Model → ${currentModel}${RST}`);
|
|
@@ -987,41 +1068,60 @@ async function cmdShell(opts, commandArgs) {
|
|
|
987
1068
|
}
|
|
988
1069
|
|
|
989
1070
|
async function cmdModels() {
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
try {
|
|
993
|
-
res = await httpRequest(url, {
|
|
994
|
-
method: 'GET',
|
|
995
|
-
headers: { 'Authorization': `Bearer ${config.api_key}` },
|
|
996
|
-
timeout: 10000,
|
|
997
|
-
}, null);
|
|
998
|
-
} catch (e) {
|
|
999
|
-
console.log(` ${FG_RED}✗ ${e.message}${RST}`);
|
|
1071
|
+
if (!config.models.length) {
|
|
1072
|
+
console.log(` ${FG_RED}✗${RST} ${FG_GRAY}No saved model profiles. Use semalt-code models add first.${RST}`);
|
|
1000
1073
|
return;
|
|
1001
1074
|
}
|
|
1002
1075
|
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
console.log(` ${FG_RED}✗ ${e.message}${RST}`);
|
|
1022
|
-
resolve();
|
|
1023
|
-
});
|
|
1076
|
+
console.log();
|
|
1077
|
+
console.log(` ${FG_TEAL}${BOLD}◆ Saved Models${RST}`);
|
|
1078
|
+
console.log(` ${FG_DARK}${'─'.repeat(40)}${RST}`);
|
|
1079
|
+
config.models.forEach((profile, index) => {
|
|
1080
|
+
const active = profile.api_base === config.api_base &&
|
|
1081
|
+
profile.api_key === config.api_key &&
|
|
1082
|
+
profile.model === config.default_model;
|
|
1083
|
+
const marker = active ? `${FG_GREEN}●${RST}` : `${FG_DARK}○${RST}`;
|
|
1084
|
+
console.log(` ${marker} ${FG_CYAN}${index + 1}.${RST} ${describeModelProfile(profile)}`);
|
|
1085
|
+
});
|
|
1086
|
+
console.log();
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
async function cmdModelsAdd() {
|
|
1090
|
+
const rl = readline.createInterface({
|
|
1091
|
+
input: process.stdin,
|
|
1092
|
+
output: process.stdout,
|
|
1093
|
+
terminal: true,
|
|
1024
1094
|
});
|
|
1095
|
+
|
|
1096
|
+
function ask(question) {
|
|
1097
|
+
return new Promise((resolve) => {
|
|
1098
|
+
rl.question(question, (answer) => resolve((answer || '').trim()));
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
console.log();
|
|
1103
|
+
console.log(` ${FG_TEAL}${BOLD}◆ Add Model Profile${RST}`);
|
|
1104
|
+
console.log(` ${FG_DARK}${'─'.repeat(40)}${RST}`);
|
|
1105
|
+
|
|
1106
|
+
const apiBase = await ask(` ${FG_CYAN}API Base URL:${RST} `);
|
|
1107
|
+
const apiKey = await ask(` ${FG_CYAN}API Key:${RST} `);
|
|
1108
|
+
const modelId = await ask(` ${FG_CYAN}Model ID:${RST} `);
|
|
1109
|
+
rl.close();
|
|
1110
|
+
|
|
1111
|
+
if (!apiBase || !modelId) {
|
|
1112
|
+
console.log(`\n ${FG_RED}✗${RST} ${FG_GRAY}API Base URL and Model ID are required.${RST}\n`);
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
const profile = {
|
|
1117
|
+
api_base: apiBase,
|
|
1118
|
+
api_key: apiKey || 'any',
|
|
1119
|
+
model: modelId,
|
|
1120
|
+
};
|
|
1121
|
+
|
|
1122
|
+
config.models.push(profile);
|
|
1123
|
+
setActiveModelProfile(profile);
|
|
1124
|
+
console.log(`\n ${FG_GREEN}✓${RST} Saved model profile: ${describeModelProfile(profile)}\n`);
|
|
1025
1125
|
}
|
|
1026
1126
|
|
|
1027
1127
|
function cmdInit(opts) {
|
|
@@ -1032,6 +1132,7 @@ function cmdInit(opts) {
|
|
|
1032
1132
|
temperature: 0.7,
|
|
1033
1133
|
max_tokens: 4096,
|
|
1034
1134
|
stream: true,
|
|
1135
|
+
models: config.models,
|
|
1035
1136
|
};
|
|
1036
1137
|
saveConfig(cfg);
|
|
1037
1138
|
config = cfg;
|
|
@@ -1085,7 +1186,8 @@ Commands:
|
|
|
1085
1186
|
code <prompt> Generate code from a prompt
|
|
1086
1187
|
edit <file> <instruction> Edit a file with AI
|
|
1087
1188
|
shell <command> Run and optionally analyze a shell command
|
|
1088
|
-
models List
|
|
1189
|
+
models List saved model profiles
|
|
1190
|
+
models add Add a saved model profile
|
|
1089
1191
|
init Initialize config
|
|
1090
1192
|
|
|
1091
1193
|
Options:
|
|
@@ -1121,7 +1223,8 @@ Config: ${CONFIG_PATH}
|
|
|
1121
1223
|
const { opts, positional } = parseArgs(rawArgs.slice(1));
|
|
1122
1224
|
await cmdShell(opts, positional);
|
|
1123
1225
|
} else if (command === 'models') {
|
|
1124
|
-
await
|
|
1226
|
+
if (rawArgs[1] === 'add') await cmdModelsAdd();
|
|
1227
|
+
else await cmdModels();
|
|
1125
1228
|
} else if (command === 'init') {
|
|
1126
1229
|
const { opts } = parseArgs(rawArgs.slice(1));
|
|
1127
1230
|
cmdInit(opts);
|