@appkit/llamacpp-cli 1.9.0 → 1.10.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/CHANGELOG.md +28 -0
- package/README.md +171 -42
- package/dist/cli.js +75 -10
- package/dist/cli.js.map +1 -1
- package/dist/commands/completion.d.ts +9 -0
- package/dist/commands/completion.d.ts.map +1 -0
- package/dist/commands/completion.js +83 -0
- package/dist/commands/completion.js.map +1 -0
- package/dist/commands/monitor.js +1 -1
- package/dist/commands/monitor.js.map +1 -1
- package/dist/commands/ps.d.ts +1 -3
- package/dist/commands/ps.d.ts.map +1 -1
- package/dist/commands/ps.js +36 -115
- package/dist/commands/ps.js.map +1 -1
- package/dist/commands/router/config.d.ts +1 -0
- package/dist/commands/router/config.d.ts.map +1 -1
- package/dist/commands/router/config.js +7 -2
- package/dist/commands/router/config.js.map +1 -1
- package/dist/commands/router/logs.d.ts +12 -0
- package/dist/commands/router/logs.d.ts.map +1 -0
- package/dist/commands/router/logs.js +238 -0
- package/dist/commands/router/logs.js.map +1 -0
- package/dist/commands/tui.d.ts +2 -0
- package/dist/commands/tui.d.ts.map +1 -0
- package/dist/commands/tui.js +27 -0
- package/dist/commands/tui.js.map +1 -0
- package/dist/lib/completion.d.ts +5 -0
- package/dist/lib/completion.d.ts.map +1 -0
- package/dist/lib/completion.js +195 -0
- package/dist/lib/completion.js.map +1 -0
- package/dist/lib/model-downloader.d.ts +5 -1
- package/dist/lib/model-downloader.d.ts.map +1 -1
- package/dist/lib/model-downloader.js +53 -20
- package/dist/lib/model-downloader.js.map +1 -1
- package/dist/lib/router-logger.d.ts +61 -0
- package/dist/lib/router-logger.d.ts.map +1 -0
- package/dist/lib/router-logger.js +200 -0
- package/dist/lib/router-logger.js.map +1 -0
- package/dist/lib/router-manager.d.ts.map +1 -1
- package/dist/lib/router-manager.js +1 -0
- package/dist/lib/router-manager.js.map +1 -1
- package/dist/lib/router-server.d.ts +9 -0
- package/dist/lib/router-server.d.ts.map +1 -1
- package/dist/lib/router-server.js +169 -57
- package/dist/lib/router-server.js.map +1 -1
- package/dist/tui/ConfigApp.d.ts +7 -0
- package/dist/tui/ConfigApp.d.ts.map +1 -0
- package/dist/tui/ConfigApp.js +1002 -0
- package/dist/tui/ConfigApp.js.map +1 -0
- package/dist/tui/HistoricalMonitorApp.d.ts.map +1 -1
- package/dist/tui/HistoricalMonitorApp.js +85 -49
- package/dist/tui/HistoricalMonitorApp.js.map +1 -1
- package/dist/tui/ModelsApp.d.ts +7 -0
- package/dist/tui/ModelsApp.d.ts.map +1 -0
- package/dist/tui/ModelsApp.js +362 -0
- package/dist/tui/ModelsApp.js.map +1 -0
- package/dist/tui/MultiServerMonitorApp.d.ts +6 -1
- package/dist/tui/MultiServerMonitorApp.d.ts.map +1 -1
- package/dist/tui/MultiServerMonitorApp.js +1038 -122
- package/dist/tui/MultiServerMonitorApp.js.map +1 -1
- package/dist/tui/RootNavigator.d.ts +7 -0
- package/dist/tui/RootNavigator.d.ts.map +1 -0
- package/dist/tui/RootNavigator.js +55 -0
- package/dist/tui/RootNavigator.js.map +1 -0
- package/dist/tui/SearchApp.d.ts +6 -0
- package/dist/tui/SearchApp.d.ts.map +1 -0
- package/dist/tui/SearchApp.js +451 -0
- package/dist/tui/SearchApp.js.map +1 -0
- package/dist/tui/SplashScreen.d.ts +16 -0
- package/dist/tui/SplashScreen.d.ts.map +1 -0
- package/dist/tui/SplashScreen.js +129 -0
- package/dist/tui/SplashScreen.js.map +1 -0
- package/dist/types/router-config.d.ts +1 -0
- package/dist/types/router-config.d.ts.map +1 -1
- package/dist/utils/log-parser.d.ts +14 -1
- package/dist/utils/log-parser.d.ts.map +1 -1
- package/dist/utils/log-parser.js +57 -26
- package/dist/utils/log-parser.js.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +41 -10
- package/src/commands/monitor.ts +1 -1
- package/src/commands/ps.ts +44 -133
- package/src/commands/router/config.ts +9 -2
- package/src/commands/router/logs.ts +256 -0
- package/src/commands/tui.ts +25 -0
- package/src/lib/model-downloader.ts +57 -20
- package/src/lib/router-logger.ts +201 -0
- package/src/lib/router-manager.ts +1 -0
- package/src/lib/router-server.ts +193 -62
- package/src/tui/ConfigApp.ts +1085 -0
- package/src/tui/HistoricalMonitorApp.ts +88 -49
- package/src/tui/ModelsApp.ts +368 -0
- package/src/tui/MultiServerMonitorApp.ts +1163 -122
- package/src/tui/RootNavigator.ts +74 -0
- package/src/tui/SearchApp.ts +511 -0
- package/src/tui/SplashScreen.ts +149 -0
- package/src/types/router-config.ts +1 -0
- package/src/utils/log-parser.ts +61 -25
package/src/lib/router-server.ts
CHANGED
|
@@ -8,6 +8,7 @@ import * as path from 'path';
|
|
|
8
8
|
import { RouterConfig } from '../types/router-config';
|
|
9
9
|
import { ServerConfig } from '../types/server-config';
|
|
10
10
|
import { readJson, fileExists, getConfigDir, getServersDir } from '../utils/file-utils';
|
|
11
|
+
import { RouterLogger, RequestTimer, RouterLogEntry } from './router-logger';
|
|
11
12
|
|
|
12
13
|
interface ErrorResponse {
|
|
13
14
|
error: string;
|
|
@@ -32,6 +33,7 @@ interface ModelsResponse {
|
|
|
32
33
|
class RouterServer {
|
|
33
34
|
private config!: RouterConfig;
|
|
34
35
|
private server!: http.Server;
|
|
36
|
+
private logger!: RouterLogger;
|
|
35
37
|
|
|
36
38
|
async initialize(): Promise<void> {
|
|
37
39
|
// Load router config
|
|
@@ -41,6 +43,12 @@ class RouterServer {
|
|
|
41
43
|
}
|
|
42
44
|
this.config = await readJson<RouterConfig>(configPath);
|
|
43
45
|
|
|
46
|
+
// Initialize logger with verbose setting
|
|
47
|
+
this.logger = new RouterLogger(this.config.verbose);
|
|
48
|
+
|
|
49
|
+
// Rotate log file if needed
|
|
50
|
+
await this.logger.rotateIfNeeded();
|
|
51
|
+
|
|
44
52
|
// Create HTTP server
|
|
45
53
|
this.server = http.createServer(async (req, res) => {
|
|
46
54
|
await this.handleRequest(req, res);
|
|
@@ -77,9 +85,6 @@ class RouterServer {
|
|
|
77
85
|
* Main request handler
|
|
78
86
|
*/
|
|
79
87
|
private async handleRequest(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
|
|
80
|
-
// Log request
|
|
81
|
-
console.error(`[Router] ${req.method} ${req.url}`);
|
|
82
|
-
|
|
83
88
|
// CORS headers
|
|
84
89
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
85
90
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
@@ -150,82 +155,145 @@ class RouterServer {
|
|
|
150
155
|
* Chat completions endpoint - route to backend server
|
|
151
156
|
*/
|
|
152
157
|
private async handleChatCompletions(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
let
|
|
158
|
+
const timer = new RequestTimer();
|
|
159
|
+
let modelName = 'unknown';
|
|
160
|
+
let statusCode = 500;
|
|
161
|
+
let errorMsg: string | undefined;
|
|
162
|
+
let promptPreview: string | undefined;
|
|
163
|
+
|
|
156
164
|
try {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
165
|
+
// Parse request body
|
|
166
|
+
const body = await this.readBody(req);
|
|
167
|
+
let requestData: any;
|
|
168
|
+
try {
|
|
169
|
+
requestData = JSON.parse(body);
|
|
170
|
+
} catch (error) {
|
|
171
|
+
statusCode = 400;
|
|
172
|
+
errorMsg = 'Invalid JSON in request body';
|
|
173
|
+
this.sendError(res, statusCode, 'Bad Request', errorMsg);
|
|
174
|
+
await this.logRequest(modelName, '/v1/chat/completions', statusCode, timer.elapsed(), errorMsg);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
162
177
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
this.sendError(res, 400, 'Bad Request', 'Missing "model" field in request');
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
178
|
+
// Extract model name and prompt preview
|
|
179
|
+
modelName = requestData.model || 'unknown';
|
|
180
|
+
promptPreview = this.extractPromptPreview(requestData);
|
|
169
181
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
182
|
+
if (!requestData.model) {
|
|
183
|
+
statusCode = 400;
|
|
184
|
+
errorMsg = 'Missing "model" field in request';
|
|
185
|
+
this.sendError(res, statusCode, 'Bad Request', errorMsg);
|
|
186
|
+
await this.logRequest(modelName, '/v1/chat/completions', statusCode, timer.elapsed(), errorMsg, undefined, promptPreview);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
176
189
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
190
|
+
// Find server for model
|
|
191
|
+
const server = await this.findServerForModel(modelName);
|
|
192
|
+
if (!server) {
|
|
193
|
+
statusCode = 404;
|
|
194
|
+
errorMsg = `No server found for model: ${modelName}`;
|
|
195
|
+
this.sendError(res, statusCode, 'Not Found', errorMsg);
|
|
196
|
+
await this.logRequest(modelName, '/v1/chat/completions', statusCode, timer.elapsed(), errorMsg, undefined, promptPreview);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
181
199
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
200
|
+
if (server.status !== 'running') {
|
|
201
|
+
statusCode = 503;
|
|
202
|
+
errorMsg = `Server for model "${modelName}" is not running`;
|
|
203
|
+
this.sendError(res, statusCode, 'Service Unavailable', errorMsg);
|
|
204
|
+
await this.logRequest(modelName, '/v1/chat/completions', statusCode, timer.elapsed(), errorMsg, `${server.host}:${server.port}`, promptPreview);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Proxy request to backend
|
|
209
|
+
const backendUrl = `http://${server.host}:${server.port}/v1/chat/completions`;
|
|
210
|
+
await this.proxyRequest(backendUrl, requestData, req, res);
|
|
211
|
+
|
|
212
|
+
// Log success
|
|
213
|
+
statusCode = 200;
|
|
214
|
+
await this.logRequest(modelName, '/v1/chat/completions', statusCode, timer.elapsed(), undefined, `${server.host}:${server.port}`, promptPreview);
|
|
215
|
+
} catch (error) {
|
|
216
|
+
errorMsg = (error as Error).message;
|
|
217
|
+
await this.logRequest(modelName, '/v1/chat/completions', statusCode, timer.elapsed(), errorMsg, undefined, promptPreview);
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
185
220
|
}
|
|
186
221
|
|
|
187
222
|
/**
|
|
188
223
|
* Embeddings endpoint - route to backend server
|
|
189
224
|
*/
|
|
190
225
|
private async handleEmbeddings(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
let
|
|
226
|
+
const timer = new RequestTimer();
|
|
227
|
+
let modelName = 'unknown';
|
|
228
|
+
let statusCode = 500;
|
|
229
|
+
let errorMsg: string | undefined;
|
|
230
|
+
let promptPreview: string | undefined;
|
|
231
|
+
|
|
194
232
|
try {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
233
|
+
// Parse request body
|
|
234
|
+
const body = await this.readBody(req);
|
|
235
|
+
let requestData: any;
|
|
236
|
+
try {
|
|
237
|
+
requestData = JSON.parse(body);
|
|
238
|
+
} catch (error) {
|
|
239
|
+
statusCode = 400;
|
|
240
|
+
errorMsg = 'Invalid JSON in request body';
|
|
241
|
+
this.sendError(res, statusCode, 'Bad Request', errorMsg);
|
|
242
|
+
await this.logRequest(modelName, '/v1/embeddings', statusCode, timer.elapsed(), errorMsg);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
200
245
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
this.sendError(res, 400, 'Bad Request', 'Missing "model" field in request');
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
246
|
+
// Extract model name and prompt preview
|
|
247
|
+
modelName = requestData.model || 'unknown';
|
|
248
|
+
promptPreview = this.extractPromptPreview(requestData);
|
|
207
249
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
250
|
+
if (!requestData.model) {
|
|
251
|
+
statusCode = 400;
|
|
252
|
+
errorMsg = 'Missing "model" field in request';
|
|
253
|
+
this.sendError(res, statusCode, 'Bad Request', errorMsg);
|
|
254
|
+
await this.logRequest(modelName, '/v1/embeddings', statusCode, timer.elapsed(), errorMsg, undefined, promptPreview);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
214
257
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
258
|
+
// Find server for model
|
|
259
|
+
const server = await this.findServerForModel(modelName);
|
|
260
|
+
if (!server) {
|
|
261
|
+
statusCode = 404;
|
|
262
|
+
errorMsg = `No server found for model: ${modelName}`;
|
|
263
|
+
this.sendError(res, statusCode, 'Not Found', errorMsg);
|
|
264
|
+
await this.logRequest(modelName, '/v1/embeddings', statusCode, timer.elapsed(), errorMsg, undefined, promptPreview);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
219
267
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
268
|
+
if (server.status !== 'running') {
|
|
269
|
+
statusCode = 503;
|
|
270
|
+
errorMsg = `Server for model "${modelName}" is not running`;
|
|
271
|
+
this.sendError(res, statusCode, 'Service Unavailable', errorMsg);
|
|
272
|
+
await this.logRequest(modelName, '/v1/embeddings', statusCode, timer.elapsed(), errorMsg, `${server.host}:${server.port}`, promptPreview);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Check if server has embeddings enabled
|
|
277
|
+
if (!server.embeddings) {
|
|
278
|
+
statusCode = 400;
|
|
279
|
+
errorMsg = `Server for model "${modelName}" does not have embeddings enabled`;
|
|
280
|
+
this.sendError(res, statusCode, 'Bad Request', errorMsg);
|
|
281
|
+
await this.logRequest(modelName, '/v1/embeddings', statusCode, timer.elapsed(), errorMsg, `${server.host}:${server.port}`, promptPreview);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
225
284
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
285
|
+
// Proxy request to backend
|
|
286
|
+
const backendUrl = `http://${server.host}:${server.port}/v1/embeddings`;
|
|
287
|
+
await this.proxyRequest(backendUrl, requestData, req, res);
|
|
288
|
+
|
|
289
|
+
// Log success
|
|
290
|
+
statusCode = 200;
|
|
291
|
+
await this.logRequest(modelName, '/v1/embeddings', statusCode, timer.elapsed(), undefined, `${server.host}:${server.port}`, promptPreview);
|
|
292
|
+
} catch (error) {
|
|
293
|
+
errorMsg = (error as Error).message;
|
|
294
|
+
await this.logRequest(modelName, '/v1/embeddings', statusCode, timer.elapsed(), errorMsg, undefined, promptPreview);
|
|
295
|
+
throw error;
|
|
296
|
+
}
|
|
229
297
|
}
|
|
230
298
|
|
|
231
299
|
/**
|
|
@@ -343,6 +411,69 @@ class RouterServer {
|
|
|
343
411
|
}
|
|
344
412
|
}
|
|
345
413
|
|
|
414
|
+
/**
|
|
415
|
+
* Helper method to log a request
|
|
416
|
+
*/
|
|
417
|
+
private async logRequest(
|
|
418
|
+
model: string,
|
|
419
|
+
endpoint: string,
|
|
420
|
+
statusCode: number,
|
|
421
|
+
durationMs: number,
|
|
422
|
+
error?: string,
|
|
423
|
+
backend?: string,
|
|
424
|
+
prompt?: string
|
|
425
|
+
): Promise<void> {
|
|
426
|
+
const entry: RouterLogEntry = {
|
|
427
|
+
timestamp: RequestTimer.now(),
|
|
428
|
+
model,
|
|
429
|
+
endpoint,
|
|
430
|
+
method: 'POST',
|
|
431
|
+
status: statusCode >= 200 && statusCode < 300 ? 'success' : 'error',
|
|
432
|
+
statusCode,
|
|
433
|
+
durationMs,
|
|
434
|
+
error,
|
|
435
|
+
backend,
|
|
436
|
+
prompt,
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
await this.logger.logRequest(entry);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Extract prompt preview from request data (first 50 chars)
|
|
444
|
+
*/
|
|
445
|
+
private extractPromptPreview(requestData: any): string | undefined {
|
|
446
|
+
try {
|
|
447
|
+
// For chat completions, get the last user message
|
|
448
|
+
if (requestData.messages && Array.isArray(requestData.messages)) {
|
|
449
|
+
const lastUserMessage = [...requestData.messages]
|
|
450
|
+
.reverse()
|
|
451
|
+
.find((msg: any) => msg.role === 'user');
|
|
452
|
+
|
|
453
|
+
if (lastUserMessage?.content) {
|
|
454
|
+
const content = typeof lastUserMessage.content === 'string'
|
|
455
|
+
? lastUserMessage.content
|
|
456
|
+
: JSON.stringify(lastUserMessage.content);
|
|
457
|
+
return content.substring(0, 50).replace(/\n/g, ' ');
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// For embeddings, get the input text
|
|
462
|
+
if (requestData.input) {
|
|
463
|
+
const input = typeof requestData.input === 'string'
|
|
464
|
+
? requestData.input
|
|
465
|
+
: Array.isArray(requestData.input)
|
|
466
|
+
? requestData.input[0]
|
|
467
|
+
: JSON.stringify(requestData.input);
|
|
468
|
+
return input.substring(0, 50).replace(/\n/g, ' ');
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return undefined;
|
|
472
|
+
} catch {
|
|
473
|
+
return undefined;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
346
477
|
/**
|
|
347
478
|
* Find a server by model name
|
|
348
479
|
*/
|