hasina-gemini-cli 1.0.0 → 1.0.2
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 +7 -5
- package/package.json +1 -1
- package/src/app.js +59 -2
- package/src/config/gemini.js +189 -63
- package/src/services/command.service.js +52 -8
package/README.md
CHANGED
|
@@ -174,10 +174,10 @@ npm start
|
|
|
174
174
|
| `/exit` | Exit the app cleanly |
|
|
175
175
|
| `/clear` | Clear in-memory history for the current session |
|
|
176
176
|
| `/history` | Show recent conversation messages |
|
|
177
|
-
| `/models` | Open a numbered Gemini model chooser |
|
|
177
|
+
| `/models` | Open a numbered Gemini model chooser with backend version info |
|
|
178
178
|
| `/save` | Persist the current session to local session storage |
|
|
179
179
|
| `/new` | Start a fresh conversation session |
|
|
180
|
-
| `/model` | Show the currently active model |
|
|
180
|
+
| `/model` | Show the currently active model with backend version details |
|
|
181
181
|
| `/use-model <model_name>` | Switch the active Gemini model at runtime |
|
|
182
182
|
| `/system` | Show the active system prompt |
|
|
183
183
|
| `/set-system <text>` | Override the current system prompt for this session |
|
|
@@ -196,11 +196,13 @@ Info > Type a message to chat, use /models to choose a model, or /help to list c
|
|
|
196
196
|
You > /models
|
|
197
197
|
Command > Choose a Gemini Model
|
|
198
198
|
01. gemini-2.5-flash [active]
|
|
199
|
-
Gemini 2.5 Flash
|
|
199
|
+
Gemini 2.5 Flash
|
|
200
|
+
version=2.5-flash | input=1M | output=65.5K
|
|
200
201
|
02. gemini-2.5-pro
|
|
201
|
-
Gemini 2.5 Pro
|
|
202
|
+
Gemini 2.5 Pro
|
|
203
|
+
version=2.5-pro | input=1M | output=65.5K
|
|
202
204
|
Model > 2
|
|
203
|
-
Success > Active model changed to "gemini-2.5-pro".
|
|
205
|
+
Success > Active model changed to "gemini-2.5-pro" (version 2.5-pro).
|
|
204
206
|
|
|
205
207
|
You > Explain event loops in Node.js.
|
|
206
208
|
Gemini > The Node.js event loop coordinates timers, I/O callbacks, microtasks,
|
package/package.json
CHANGED
package/src/app.js
CHANGED
|
@@ -13,6 +13,32 @@ function normalizeAssistantText(text) {
|
|
|
13
13
|
return text.trim();
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
function normalizeIntentText(text) {
|
|
17
|
+
return String(text)
|
|
18
|
+
.normalize('NFD')
|
|
19
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
20
|
+
.toLowerCase()
|
|
21
|
+
.trim();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isModelIdentityQuestion(text) {
|
|
25
|
+
const normalized = normalizeIntentText(text);
|
|
26
|
+
const patterns = [
|
|
27
|
+
/identifiant.*modele/,
|
|
28
|
+
/id.*modele/,
|
|
29
|
+
/modele.*exact/,
|
|
30
|
+
/quel.*modele.*(utilise|actif|es)/,
|
|
31
|
+
/donne.*modele/,
|
|
32
|
+
/model.*identifier/,
|
|
33
|
+
/exact.*model.*id/,
|
|
34
|
+
/what.*model.*are.*you/,
|
|
35
|
+
/which.*model.*are.*you/,
|
|
36
|
+
/model.*id/,
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
return patterns.some((pattern) => pattern.test(normalized));
|
|
40
|
+
}
|
|
41
|
+
|
|
16
42
|
class App {
|
|
17
43
|
constructor({ config, provider, printer }) {
|
|
18
44
|
this.config = config;
|
|
@@ -49,6 +75,10 @@ class App {
|
|
|
49
75
|
sessionId: await this.sessionService.generateSessionId(),
|
|
50
76
|
sessionCreatedAt: new Date().toISOString(),
|
|
51
77
|
model: this.config.defaultModel,
|
|
78
|
+
activeModelInfo: {
|
|
79
|
+
id: this.config.defaultModel,
|
|
80
|
+
version: null,
|
|
81
|
+
},
|
|
52
82
|
systemPrompt: this.config.systemPrompt,
|
|
53
83
|
historyService: this.historyService,
|
|
54
84
|
};
|
|
@@ -177,12 +207,15 @@ class App {
|
|
|
177
207
|
}
|
|
178
208
|
|
|
179
209
|
if (selectedModel.id === this.state.model) {
|
|
180
|
-
|
|
210
|
+
const versionSuffix = selectedModel.version ? ` (version ${selectedModel.version})` : '';
|
|
211
|
+
this.printer.printInfo(`"${selectedModel.id}" is already the active model${versionSuffix}.`);
|
|
181
212
|
return false;
|
|
182
213
|
}
|
|
183
214
|
|
|
184
215
|
this.state.model = selectedModel.id;
|
|
185
|
-
this.
|
|
216
|
+
this.state.activeModelInfo = selectedModel;
|
|
217
|
+
const versionSuffix = selectedModel.version ? ` (version ${selectedModel.version})` : '';
|
|
218
|
+
this.printer.printSuccess(`Active model changed to "${selectedModel.id}"${versionSuffix}.`);
|
|
186
219
|
return false;
|
|
187
220
|
}
|
|
188
221
|
|
|
@@ -206,6 +239,12 @@ class App {
|
|
|
206
239
|
}
|
|
207
240
|
|
|
208
241
|
async handleChatTurn(userInput) {
|
|
242
|
+
const handledLocally = await this.tryHandleLocalModelIdentityQuestion(userInput);
|
|
243
|
+
|
|
244
|
+
if (handledLocally) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
209
248
|
const loading = this.printer.createLoadingIndicator();
|
|
210
249
|
let streamStarted = false;
|
|
211
250
|
|
|
@@ -257,6 +296,24 @@ class App {
|
|
|
257
296
|
}
|
|
258
297
|
}
|
|
259
298
|
|
|
299
|
+
async tryHandleLocalModelIdentityQuestion(userInput) {
|
|
300
|
+
if (!isModelIdentityQuestion(userInput)) {
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const modelInfo = this.state.activeModelInfo || { id: this.state.model, version: null };
|
|
305
|
+
let message = `Identifiant du modele (local): ${modelInfo.id || this.state.model}.`;
|
|
306
|
+
|
|
307
|
+
if (modelInfo?.version) {
|
|
308
|
+
message = `Identifiant du modele (local): ${modelInfo.id} (version ${modelInfo.version}).`;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
this.printer.printAssistant(message);
|
|
312
|
+
this.state.historyService.addMessage('user', userInput);
|
|
313
|
+
this.state.historyService.addMessage('assistant', message);
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
|
|
260
317
|
async shutdown() {
|
|
261
318
|
if (this.readline) {
|
|
262
319
|
try {
|
package/src/config/gemini.js
CHANGED
|
@@ -10,6 +10,8 @@ const NON_CHAT_MODEL_KEYWORDS = [
|
|
|
10
10
|
'robotics',
|
|
11
11
|
'computer-use',
|
|
12
12
|
];
|
|
13
|
+
const TRANSIENT_STATUS_CODES = new Set([429, 500, 502, 503, 504]);
|
|
14
|
+
const RETRY_DELAYS_MS = [1200, 2500];
|
|
13
15
|
|
|
14
16
|
function buildGeminiChatParams({ model, systemPrompt, history }) {
|
|
15
17
|
const params = {
|
|
@@ -53,7 +55,72 @@ function extractResponseText(response) {
|
|
|
53
55
|
.join('');
|
|
54
56
|
}
|
|
55
57
|
|
|
56
|
-
function
|
|
58
|
+
function sleep(durationMs) {
|
|
59
|
+
return new Promise((resolve) => {
|
|
60
|
+
setTimeout(resolve, durationMs);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getErrorStatus(error) {
|
|
65
|
+
if (typeof error?.status === 'number') {
|
|
66
|
+
return error.status;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (typeof error?.cause?.status === 'number') {
|
|
70
|
+
return error.cause.status;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function getErrorMessage(error) {
|
|
77
|
+
if (typeof error?.message === 'string' && error.message.trim()) {
|
|
78
|
+
return error.message.trim();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (typeof error?.cause?.message === 'string' && error.cause.message.trim()) {
|
|
82
|
+
return error.cause.message.trim();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return '';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function isPreviewModel(model) {
|
|
89
|
+
return typeof model === 'string' && /preview|exp|experimental/i.test(model);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function isTransientGeminiError(error) {
|
|
93
|
+
const status = getErrorStatus(error);
|
|
94
|
+
const lowerMessage = getErrorMessage(error).toLowerCase();
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
TRANSIENT_STATUS_CODES.has(status) ||
|
|
98
|
+
lowerMessage.includes('high demand') ||
|
|
99
|
+
lowerMessage.includes('service unavailable') ||
|
|
100
|
+
lowerMessage.includes('temporarily unavailable') ||
|
|
101
|
+
lowerMessage.includes('unavailable') ||
|
|
102
|
+
lowerMessage.includes('rate limit') ||
|
|
103
|
+
lowerMessage.includes('fetch failed') ||
|
|
104
|
+
lowerMessage.includes('timeout') ||
|
|
105
|
+
lowerMessage.includes('timed out') ||
|
|
106
|
+
lowerMessage.includes('econnreset') ||
|
|
107
|
+
lowerMessage.includes('enotfound')
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function buildTemporaryUnavailableMessage(model) {
|
|
112
|
+
const baseMessage = model
|
|
113
|
+
? `Gemini is temporarily unavailable for "${model}".`
|
|
114
|
+
: 'Gemini is temporarily unavailable.';
|
|
115
|
+
|
|
116
|
+
if (isPreviewModel(model)) {
|
|
117
|
+
return `${baseMessage} Preview models can be under heavy demand. Retry in a few moments or switch with /use-model gemini-2.5-flash.`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return `${baseMessage} Try again in a few moments.`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function normalizeModelInfo(model) {
|
|
57
124
|
const id = String(model?.name || '').replace(/^models\//, '').trim();
|
|
58
125
|
|
|
59
126
|
if (!id) {
|
|
@@ -62,7 +129,9 @@ function normalizeListedModel(model) {
|
|
|
62
129
|
|
|
63
130
|
return {
|
|
64
131
|
id,
|
|
132
|
+
apiName: String(model.name || `models/${id}`),
|
|
65
133
|
displayName: model.displayName || id,
|
|
134
|
+
version: typeof model.version === 'string' && model.version.trim() ? model.version.trim() : null,
|
|
66
135
|
description: model.description || '',
|
|
67
136
|
inputTokenLimit: Number.isFinite(model.inputTokenLimit) ? model.inputTokenLimit : null,
|
|
68
137
|
outputTokenLimit: Number.isFinite(model.outputTokenLimit) ? model.outputTokenLimit : null,
|
|
@@ -100,9 +169,10 @@ function sortModels(models, currentModel) {
|
|
|
100
169
|
});
|
|
101
170
|
}
|
|
102
171
|
|
|
103
|
-
function createFriendlyGeminiError(error, fallbackMessage) {
|
|
104
|
-
const
|
|
105
|
-
const
|
|
172
|
+
function createFriendlyGeminiError(error, fallbackMessage, options = {}) {
|
|
173
|
+
const model = options.model;
|
|
174
|
+
const status = getErrorStatus(error);
|
|
175
|
+
const message = getErrorMessage(error);
|
|
106
176
|
const lowerMessage = message.toLowerCase();
|
|
107
177
|
|
|
108
178
|
if (status === 400) {
|
|
@@ -152,7 +222,7 @@ function createFriendlyGeminiError(error, fallbackMessage) {
|
|
|
152
222
|
}
|
|
153
223
|
|
|
154
224
|
if (status >= 500) {
|
|
155
|
-
return new Error(
|
|
225
|
+
return new Error(buildTemporaryUnavailableMessage(model), {
|
|
156
226
|
cause: error,
|
|
157
227
|
});
|
|
158
228
|
}
|
|
@@ -178,6 +248,26 @@ function createFriendlyGeminiError(error, fallbackMessage) {
|
|
|
178
248
|
function createGeminiProvider({ apiKey }) {
|
|
179
249
|
const client = new GoogleGenAI({ apiKey });
|
|
180
250
|
|
|
251
|
+
async function retryOperation(operation, { model, fallbackMessage }) {
|
|
252
|
+
let lastError = null;
|
|
253
|
+
|
|
254
|
+
for (let attempt = 0; attempt <= RETRY_DELAYS_MS.length; attempt += 1) {
|
|
255
|
+
try {
|
|
256
|
+
return await operation(attempt);
|
|
257
|
+
} catch (error) {
|
|
258
|
+
lastError = error;
|
|
259
|
+
|
|
260
|
+
if (!isTransientGeminiError(error) || attempt >= RETRY_DELAYS_MS.length) {
|
|
261
|
+
throw createFriendlyGeminiError(error, fallbackMessage, { model });
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
await sleep(RETRY_DELAYS_MS[attempt]);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
throw createFriendlyGeminiError(lastError, fallbackMessage, { model });
|
|
269
|
+
}
|
|
270
|
+
|
|
181
271
|
return {
|
|
182
272
|
name: 'gemini',
|
|
183
273
|
|
|
@@ -189,7 +279,23 @@ function createGeminiProvider({ apiKey }) {
|
|
|
189
279
|
} catch (error) {
|
|
190
280
|
throw createFriendlyGeminiError(
|
|
191
281
|
error,
|
|
192
|
-
`Unable to validate model "${normalizedModel}"
|
|
282
|
+
`Unable to validate model "${normalizedModel}".`,
|
|
283
|
+
{ model: normalizedModel }
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
async getModelInfo(model) {
|
|
289
|
+
const normalizedModel = normalizeModelName(model);
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
const response = await client.models.get({ model: normalizedModel });
|
|
293
|
+
return normalizeModelInfo(response);
|
|
294
|
+
} catch (error) {
|
|
295
|
+
throw createFriendlyGeminiError(
|
|
296
|
+
error,
|
|
297
|
+
`Unable to load details for model "${normalizedModel}".`,
|
|
298
|
+
{ model: normalizedModel }
|
|
193
299
|
);
|
|
194
300
|
}
|
|
195
301
|
},
|
|
@@ -204,7 +310,7 @@ function createGeminiProvider({ apiKey }) {
|
|
|
204
310
|
const models = [];
|
|
205
311
|
|
|
206
312
|
for await (const model of pager) {
|
|
207
|
-
const normalized =
|
|
313
|
+
const normalized = normalizeModelInfo(model);
|
|
208
314
|
|
|
209
315
|
if (!isChatCapableModel(normalized)) {
|
|
210
316
|
continue;
|
|
@@ -220,70 +326,90 @@ function createGeminiProvider({ apiKey }) {
|
|
|
220
326
|
},
|
|
221
327
|
|
|
222
328
|
async generateReply({ model, systemPrompt, history, message }) {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
329
|
+
return retryOperation(
|
|
330
|
+
async () => {
|
|
331
|
+
const chat = client.chats.create(
|
|
332
|
+
buildGeminiChatParams({
|
|
333
|
+
model,
|
|
334
|
+
systemPrompt,
|
|
335
|
+
history,
|
|
336
|
+
})
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
const response = await chat.sendMessage({ message });
|
|
340
|
+
|
|
341
|
+
return {
|
|
226
342
|
model,
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const response = await chat.sendMessage({ message });
|
|
233
|
-
|
|
234
|
-
return {
|
|
343
|
+
streamed: false,
|
|
344
|
+
text: extractResponseText(response),
|
|
345
|
+
};
|
|
346
|
+
},
|
|
347
|
+
{
|
|
235
348
|
model,
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
} catch (error) {
|
|
240
|
-
throw createFriendlyGeminiError(error, 'Gemini request failed.');
|
|
241
|
-
}
|
|
349
|
+
fallbackMessage: 'Gemini request failed.',
|
|
350
|
+
}
|
|
351
|
+
);
|
|
242
352
|
},
|
|
243
353
|
|
|
244
354
|
async streamReply({ model, systemPrompt, history, message, onTextChunk }) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
355
|
+
return retryOperation(
|
|
356
|
+
async () => {
|
|
357
|
+
const chat = client.chats.create(
|
|
358
|
+
buildGeminiChatParams({
|
|
359
|
+
model,
|
|
360
|
+
systemPrompt,
|
|
361
|
+
history,
|
|
362
|
+
})
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
const stream = await chat.sendMessageStream({ message });
|
|
366
|
+
let fullText = '';
|
|
367
|
+
let emittedAnyChunk = false;
|
|
368
|
+
|
|
369
|
+
try {
|
|
370
|
+
for await (const chunk of stream) {
|
|
371
|
+
const chunkText = extractResponseText(chunk);
|
|
372
|
+
|
|
373
|
+
if (!chunkText) {
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const delta = chunkText.startsWith(fullText)
|
|
378
|
+
? chunkText.slice(fullText.length)
|
|
379
|
+
: chunkText;
|
|
380
|
+
|
|
381
|
+
if (!delta) {
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
fullText += delta;
|
|
386
|
+
emittedAnyChunk = true;
|
|
387
|
+
|
|
388
|
+
if (typeof onTextChunk === 'function') {
|
|
389
|
+
onTextChunk(delta);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
} catch (error) {
|
|
393
|
+
if (emittedAnyChunk) {
|
|
394
|
+
throw createFriendlyGeminiError(error, 'Gemini streaming request failed.', {
|
|
395
|
+
model,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
throw error;
|
|
270
400
|
}
|
|
271
401
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
return {
|
|
402
|
+
return {
|
|
403
|
+
model,
|
|
404
|
+
streamed: true,
|
|
405
|
+
text: fullText,
|
|
406
|
+
};
|
|
407
|
+
},
|
|
408
|
+
{
|
|
280
409
|
model,
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
} catch (error) {
|
|
285
|
-
throw createFriendlyGeminiError(error, 'Gemini streaming request failed.');
|
|
286
|
-
}
|
|
410
|
+
fallbackMessage: 'Gemini streaming request failed.',
|
|
411
|
+
}
|
|
412
|
+
);
|
|
287
413
|
},
|
|
288
414
|
};
|
|
289
415
|
}
|
|
@@ -68,6 +68,10 @@ function formatTokenCount(value) {
|
|
|
68
68
|
return String(value);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
function formatModelVersion(model) {
|
|
72
|
+
return model?.version || 'unknown';
|
|
73
|
+
}
|
|
74
|
+
|
|
71
75
|
class CommandService {
|
|
72
76
|
constructor({ sessionService, provider }) {
|
|
73
77
|
this.sessionService = sessionService;
|
|
@@ -129,7 +133,7 @@ class CommandService {
|
|
|
129
133
|
return this.handleNew(state);
|
|
130
134
|
|
|
131
135
|
case 'model':
|
|
132
|
-
return
|
|
136
|
+
return this.handleModel(state);
|
|
133
137
|
|
|
134
138
|
case 'use-model':
|
|
135
139
|
return this.handleUseModel(state, command.rawArgs);
|
|
@@ -203,16 +207,49 @@ class CommandService {
|
|
|
203
207
|
const header = `${String(index + 1).padStart(2, '0')}. ${model.id}${
|
|
204
208
|
tags.length ? ` [${tags.join(', ')}]` : ''
|
|
205
209
|
}`;
|
|
206
|
-
const details =
|
|
207
|
-
model.
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
210
|
+
const details = [
|
|
211
|
+
compactText(model.displayName, 64),
|
|
212
|
+
`version=${formatModelVersion(model)} | input=${formatTokenCount(
|
|
213
|
+
model.inputTokenLimit
|
|
214
|
+
)} | output=${formatTokenCount(model.outputTokenLimit)}`,
|
|
215
|
+
].map((line) => ` ${line}`).join('\n');
|
|
216
|
+
|
|
217
|
+
return `${header}\n${details}`;
|
|
211
218
|
});
|
|
212
219
|
|
|
213
220
|
return modelPickerResult('Choose a Gemini Model', lines, models);
|
|
214
221
|
}
|
|
215
222
|
|
|
223
|
+
async handleModel(state) {
|
|
224
|
+
if (typeof this.provider.getModelInfo !== 'function') {
|
|
225
|
+
return messageResult('info', `Active model: ${state.model}`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const model = await this.provider.getModelInfo(state.model);
|
|
229
|
+
state.activeModelInfo = model;
|
|
230
|
+
const tags = [];
|
|
231
|
+
|
|
232
|
+
if (model.isPreview) {
|
|
233
|
+
tags.push('preview');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (model.isLatest) {
|
|
237
|
+
tags.push('latest');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const lines = [
|
|
241
|
+
`id=${model.id}${tags.length ? ` [${tags.join(', ')}]` : ''}`,
|
|
242
|
+
`display=${model.displayName}`,
|
|
243
|
+
`version=${formatModelVersion(model)}`,
|
|
244
|
+
`api=${model.apiName}`,
|
|
245
|
+
`input=${formatTokenCount(model.inputTokenLimit)} | output=${formatTokenCount(
|
|
246
|
+
model.outputTokenLimit
|
|
247
|
+
)}`,
|
|
248
|
+
];
|
|
249
|
+
|
|
250
|
+
return blockResult('Active Model', lines);
|
|
251
|
+
}
|
|
252
|
+
|
|
216
253
|
async handleSave(state) {
|
|
217
254
|
const savedSession = await this.sessionService.saveSession({
|
|
218
255
|
id: state.sessionId,
|
|
@@ -244,10 +281,16 @@ class CommandService {
|
|
|
244
281
|
assertCommandArgument(rawArgs, 'Usage: /use-model <model_name>')
|
|
245
282
|
);
|
|
246
283
|
|
|
247
|
-
|
|
284
|
+
const model = typeof this.provider.getModelInfo === 'function'
|
|
285
|
+
? await this.provider.getModelInfo(modelName)
|
|
286
|
+
: await this.provider.validateModel(modelName);
|
|
248
287
|
state.model = modelName;
|
|
288
|
+
state.activeModelInfo = model?.id ? model : { id: modelName, version: null };
|
|
249
289
|
|
|
250
|
-
return messageResult(
|
|
290
|
+
return messageResult(
|
|
291
|
+
'success',
|
|
292
|
+
`Active model changed to "${modelName}" (version ${formatModelVersion(model)}).`
|
|
293
|
+
);
|
|
251
294
|
}
|
|
252
295
|
|
|
253
296
|
handleSetSystem(state, rawArgs) {
|
|
@@ -284,6 +327,7 @@ class CommandService {
|
|
|
284
327
|
state.sessionId = session.id;
|
|
285
328
|
state.sessionCreatedAt = session.createdAt;
|
|
286
329
|
state.model = session.model;
|
|
330
|
+
state.activeModelInfo = { id: session.model, version: null };
|
|
287
331
|
state.systemPrompt = session.systemPrompt;
|
|
288
332
|
|
|
289
333
|
return messageResult(
|