@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.
Files changed (98) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +171 -42
  3. package/dist/cli.js +75 -10
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/completion.d.ts +9 -0
  6. package/dist/commands/completion.d.ts.map +1 -0
  7. package/dist/commands/completion.js +83 -0
  8. package/dist/commands/completion.js.map +1 -0
  9. package/dist/commands/monitor.js +1 -1
  10. package/dist/commands/monitor.js.map +1 -1
  11. package/dist/commands/ps.d.ts +1 -3
  12. package/dist/commands/ps.d.ts.map +1 -1
  13. package/dist/commands/ps.js +36 -115
  14. package/dist/commands/ps.js.map +1 -1
  15. package/dist/commands/router/config.d.ts +1 -0
  16. package/dist/commands/router/config.d.ts.map +1 -1
  17. package/dist/commands/router/config.js +7 -2
  18. package/dist/commands/router/config.js.map +1 -1
  19. package/dist/commands/router/logs.d.ts +12 -0
  20. package/dist/commands/router/logs.d.ts.map +1 -0
  21. package/dist/commands/router/logs.js +238 -0
  22. package/dist/commands/router/logs.js.map +1 -0
  23. package/dist/commands/tui.d.ts +2 -0
  24. package/dist/commands/tui.d.ts.map +1 -0
  25. package/dist/commands/tui.js +27 -0
  26. package/dist/commands/tui.js.map +1 -0
  27. package/dist/lib/completion.d.ts +5 -0
  28. package/dist/lib/completion.d.ts.map +1 -0
  29. package/dist/lib/completion.js +195 -0
  30. package/dist/lib/completion.js.map +1 -0
  31. package/dist/lib/model-downloader.d.ts +5 -1
  32. package/dist/lib/model-downloader.d.ts.map +1 -1
  33. package/dist/lib/model-downloader.js +53 -20
  34. package/dist/lib/model-downloader.js.map +1 -1
  35. package/dist/lib/router-logger.d.ts +61 -0
  36. package/dist/lib/router-logger.d.ts.map +1 -0
  37. package/dist/lib/router-logger.js +200 -0
  38. package/dist/lib/router-logger.js.map +1 -0
  39. package/dist/lib/router-manager.d.ts.map +1 -1
  40. package/dist/lib/router-manager.js +1 -0
  41. package/dist/lib/router-manager.js.map +1 -1
  42. package/dist/lib/router-server.d.ts +9 -0
  43. package/dist/lib/router-server.d.ts.map +1 -1
  44. package/dist/lib/router-server.js +169 -57
  45. package/dist/lib/router-server.js.map +1 -1
  46. package/dist/tui/ConfigApp.d.ts +7 -0
  47. package/dist/tui/ConfigApp.d.ts.map +1 -0
  48. package/dist/tui/ConfigApp.js +1002 -0
  49. package/dist/tui/ConfigApp.js.map +1 -0
  50. package/dist/tui/HistoricalMonitorApp.d.ts.map +1 -1
  51. package/dist/tui/HistoricalMonitorApp.js +85 -49
  52. package/dist/tui/HistoricalMonitorApp.js.map +1 -1
  53. package/dist/tui/ModelsApp.d.ts +7 -0
  54. package/dist/tui/ModelsApp.d.ts.map +1 -0
  55. package/dist/tui/ModelsApp.js +362 -0
  56. package/dist/tui/ModelsApp.js.map +1 -0
  57. package/dist/tui/MultiServerMonitorApp.d.ts +6 -1
  58. package/dist/tui/MultiServerMonitorApp.d.ts.map +1 -1
  59. package/dist/tui/MultiServerMonitorApp.js +1038 -122
  60. package/dist/tui/MultiServerMonitorApp.js.map +1 -1
  61. package/dist/tui/RootNavigator.d.ts +7 -0
  62. package/dist/tui/RootNavigator.d.ts.map +1 -0
  63. package/dist/tui/RootNavigator.js +55 -0
  64. package/dist/tui/RootNavigator.js.map +1 -0
  65. package/dist/tui/SearchApp.d.ts +6 -0
  66. package/dist/tui/SearchApp.d.ts.map +1 -0
  67. package/dist/tui/SearchApp.js +451 -0
  68. package/dist/tui/SearchApp.js.map +1 -0
  69. package/dist/tui/SplashScreen.d.ts +16 -0
  70. package/dist/tui/SplashScreen.d.ts.map +1 -0
  71. package/dist/tui/SplashScreen.js +129 -0
  72. package/dist/tui/SplashScreen.js.map +1 -0
  73. package/dist/types/router-config.d.ts +1 -0
  74. package/dist/types/router-config.d.ts.map +1 -1
  75. package/dist/utils/log-parser.d.ts +14 -1
  76. package/dist/utils/log-parser.d.ts.map +1 -1
  77. package/dist/utils/log-parser.js +57 -26
  78. package/dist/utils/log-parser.js.map +1 -1
  79. package/package.json +1 -1
  80. package/src/cli.ts +41 -10
  81. package/src/commands/monitor.ts +1 -1
  82. package/src/commands/ps.ts +44 -133
  83. package/src/commands/router/config.ts +9 -2
  84. package/src/commands/router/logs.ts +256 -0
  85. package/src/commands/tui.ts +25 -0
  86. package/src/lib/model-downloader.ts +57 -20
  87. package/src/lib/router-logger.ts +201 -0
  88. package/src/lib/router-manager.ts +1 -0
  89. package/src/lib/router-server.ts +193 -62
  90. package/src/tui/ConfigApp.ts +1085 -0
  91. package/src/tui/HistoricalMonitorApp.ts +88 -49
  92. package/src/tui/ModelsApp.ts +368 -0
  93. package/src/tui/MultiServerMonitorApp.ts +1163 -122
  94. package/src/tui/RootNavigator.ts +74 -0
  95. package/src/tui/SearchApp.ts +511 -0
  96. package/src/tui/SplashScreen.ts +149 -0
  97. package/src/types/router-config.ts +1 -0
  98. package/src/utils/log-parser.ts +61 -25
@@ -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
- // Parse request body
154
- const body = await this.readBody(req);
155
- let requestData: any;
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
- requestData = JSON.parse(body);
158
- } catch (error) {
159
- this.sendError(res, 400, 'Bad Request', 'Invalid JSON in request body');
160
- return;
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
- // Extract model name
164
- const modelName = requestData.model;
165
- if (!modelName) {
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
- // Find server for model
171
- const server = await this.findServerForModel(modelName);
172
- if (!server) {
173
- this.sendError(res, 404, 'Not Found', `No server found for model: ${modelName}`);
174
- return;
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
- if (server.status !== 'running') {
178
- this.sendError(res, 503, 'Service Unavailable', `Server for model "${modelName}" is not running`);
179
- return;
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
- // Proxy request to backend
183
- const backendUrl = `http://${server.host}:${server.port}/v1/chat/completions`;
184
- await this.proxyRequest(backendUrl, requestData, req, res);
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
- // Parse request body
192
- const body = await this.readBody(req);
193
- let requestData: any;
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
- requestData = JSON.parse(body);
196
- } catch (error) {
197
- this.sendError(res, 400, 'Bad Request', 'Invalid JSON in request body');
198
- return;
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
- // Extract model name
202
- const modelName = requestData.model;
203
- if (!modelName) {
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
- // Find server for model
209
- const server = await this.findServerForModel(modelName);
210
- if (!server) {
211
- this.sendError(res, 404, 'Not Found', `No server found for model: ${modelName}`);
212
- return;
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
- if (server.status !== 'running') {
216
- this.sendError(res, 503, 'Service Unavailable', `Server for model "${modelName}" is not running`);
217
- return;
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
- // Check if server has embeddings enabled
221
- if (!server.embeddings) {
222
- this.sendError(res, 400, 'Bad Request', `Server for model "${modelName}" does not have embeddings enabled`);
223
- return;
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
- // Proxy request to backend
227
- const backendUrl = `http://${server.host}:${server.port}/v1/embeddings`;
228
- await this.proxyRequest(backendUrl, requestData, req, res);
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
  */