@vfarcic/dot-ai 1.2.3 → 1.3.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/dist/core/embedding-service.d.ts +1 -1
- package/dist/core/embedding-service.d.ts.map +1 -1
- package/dist/core/embedding-service.js +65 -36
- package/dist/core/model-config.d.ts +3 -3
- package/dist/core/model-config.js +3 -3
- package/dist/interfaces/embedding-migration-handler.d.ts +16 -0
- package/dist/interfaces/embedding-migration-handler.d.ts.map +1 -0
- package/dist/interfaces/embedding-migration-handler.js +296 -0
- package/dist/interfaces/rest-api.d.ts +4 -0
- package/dist/interfaces/rest-api.d.ts.map +1 -1
- package/dist/interfaces/rest-api.js +259 -132
- package/dist/interfaces/routes/index.d.ts.map +1 -1
- package/dist/interfaces/routes/index.js +62 -12
- package/dist/interfaces/schemas/common.d.ts +1 -0
- package/dist/interfaces/schemas/common.d.ts.map +1 -1
- package/dist/interfaces/schemas/common.js +1 -0
- package/dist/interfaces/schemas/embeddings.d.ts +138 -0
- package/dist/interfaces/schemas/embeddings.d.ts.map +1 -0
- package/dist/interfaces/schemas/embeddings.js +79 -0
- package/dist/interfaces/schemas/index.d.ts +1 -0
- package/dist/interfaces/schemas/index.d.ts.map +1 -1
- package/dist/interfaces/schemas/index.js +10 -1
- package/dist/tools/operate-analysis.js +9 -2
- package/dist/tools/remediate.d.ts.map +1 -1
- package/dist/tools/remediate.js +6 -1
- package/package.json +1 -1
- package/prompts/remediate-system.md +1 -1
|
@@ -45,6 +45,7 @@ const openapi_generator_1 = require("./openapi-generator");
|
|
|
45
45
|
const rest_route_registry_1 = require("./rest-route-registry");
|
|
46
46
|
const routes_1 = require("./routes");
|
|
47
47
|
const resource_sync_handler_1 = require("./resource-sync-handler");
|
|
48
|
+
const embedding_migration_handler_1 = require("./embedding-migration-handler");
|
|
48
49
|
const prompts_1 = require("../tools/prompts");
|
|
49
50
|
const generic_session_manager_1 = require("../core/generic-session-manager");
|
|
50
51
|
const shared_prompt_loader_1 = require("../core/shared-prompt-loader");
|
|
@@ -90,7 +91,7 @@ class RestApiRouter {
|
|
|
90
91
|
version: 'v1',
|
|
91
92
|
enableCors: true,
|
|
92
93
|
requestTimeout: 1800000, // 30 minutes for long-running operations (capability scan with slower AI providers)
|
|
93
|
-
...config
|
|
94
|
+
...config,
|
|
94
95
|
};
|
|
95
96
|
// Initialize route registry and register all routes (PRD #354)
|
|
96
97
|
this.routeRegistry = new rest_route_registry_1.RestRouteRegistry(logger);
|
|
@@ -118,7 +119,7 @@ class RestApiRouter {
|
|
|
118
119
|
requestId,
|
|
119
120
|
method: req.method,
|
|
120
121
|
url: req.url,
|
|
121
|
-
hasBody: !!body
|
|
122
|
+
hasBody: !!body,
|
|
122
123
|
});
|
|
123
124
|
// Handle CORS preflight
|
|
124
125
|
if (this.config.enableCors) {
|
|
@@ -162,7 +163,7 @@ class RestApiRouter {
|
|
|
162
163
|
catch (error) {
|
|
163
164
|
this.logger.error('REST API request failed', error instanceof Error ? error : new Error(String(error)), {
|
|
164
165
|
requestId,
|
|
165
|
-
errorMessage: error instanceof Error ? error.message : String(error)
|
|
166
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
166
167
|
});
|
|
167
168
|
await this.sendErrorResponse(res, requestId, HttpStatus.INTERNAL_SERVER_ERROR, 'INTERNAL_ERROR', 'An internal server error occurred');
|
|
168
169
|
}
|
|
@@ -193,13 +194,17 @@ class RestApiRouter {
|
|
|
193
194
|
'GET:/api/v1/sessions/:sessionId': () => this.handleSessionRetrieval(req, res, requestId, params.sessionId),
|
|
194
195
|
'DELETE:/api/v1/knowledge/source/:sourceIdentifier': () => this.handleDeleteKnowledgeSource(req, res, requestId, params.sourceIdentifier),
|
|
195
196
|
'POST:/api/v1/knowledge/ask': () => this.handleKnowledgeAsk(req, res, requestId, body),
|
|
197
|
+
'POST:/api/v1/embeddings/migrate': () => this.handleEmbeddingMigrationRequest(req, res, requestId, body),
|
|
196
198
|
};
|
|
197
199
|
const handler = handlers[routeKey];
|
|
198
200
|
if (handler) {
|
|
199
201
|
await handler();
|
|
200
202
|
}
|
|
201
203
|
else {
|
|
202
|
-
this.logger.warn('Route matched but no handler found', {
|
|
204
|
+
this.logger.warn('Route matched but no handler found', {
|
|
205
|
+
requestId,
|
|
206
|
+
routeKey,
|
|
207
|
+
});
|
|
203
208
|
await this.sendErrorResponse(res, requestId, HttpStatus.NOT_FOUND, 'NOT_FOUND', 'Handler not found for route');
|
|
204
209
|
}
|
|
205
210
|
}
|
|
@@ -220,19 +225,19 @@ class RestApiRouter {
|
|
|
220
225
|
tools,
|
|
221
226
|
total: tools.length,
|
|
222
227
|
categories,
|
|
223
|
-
tags
|
|
228
|
+
tags,
|
|
224
229
|
},
|
|
225
230
|
meta: {
|
|
226
231
|
timestamp: new Date().toISOString(),
|
|
227
232
|
requestId,
|
|
228
|
-
version: this.config.version
|
|
229
|
-
}
|
|
233
|
+
version: this.config.version,
|
|
234
|
+
},
|
|
230
235
|
};
|
|
231
236
|
await this.sendJsonResponse(res, HttpStatus.OK, response);
|
|
232
237
|
this.logger.info('Tool discovery request completed', {
|
|
233
238
|
requestId,
|
|
234
239
|
totalTools: tools.length,
|
|
235
|
-
filters: { category, tag, search }
|
|
240
|
+
filters: { category, tag, search },
|
|
236
241
|
});
|
|
237
242
|
}
|
|
238
243
|
catch {
|
|
@@ -258,7 +263,7 @@ class RestApiRouter {
|
|
|
258
263
|
this.logger.info('Executing tool via REST API', {
|
|
259
264
|
requestId,
|
|
260
265
|
toolName,
|
|
261
|
-
parameters: Object.keys(body)
|
|
266
|
+
parameters: Object.keys(body),
|
|
262
267
|
});
|
|
263
268
|
// Execute the tool handler with timeout
|
|
264
269
|
// Note: Tool handlers expect the same format as MCP calls
|
|
@@ -268,7 +273,7 @@ class RestApiRouter {
|
|
|
268
273
|
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Request timeout exceeded')), timeoutMs));
|
|
269
274
|
// Prevent unhandled rejection if toolPromise resolves after timeout
|
|
270
275
|
toolPromise.catch(() => { });
|
|
271
|
-
const mcpResult = await Promise.race([toolPromise, timeoutPromise]);
|
|
276
|
+
const mcpResult = (await Promise.race([toolPromise, timeoutPromise]));
|
|
272
277
|
// Transform MCP format to proper REST JSON
|
|
273
278
|
// All MCP tools return JSON.stringify() in content[0].text, so parse it back to proper JSON
|
|
274
279
|
let transformedResult;
|
|
@@ -280,7 +285,9 @@ class RestApiRouter {
|
|
|
280
285
|
this.logger.warn('Failed to parse MCP tool result as JSON, returning as text', {
|
|
281
286
|
requestId,
|
|
282
287
|
toolName,
|
|
283
|
-
error: parseError instanceof Error
|
|
288
|
+
error: parseError instanceof Error
|
|
289
|
+
? parseError.message
|
|
290
|
+
: String(parseError),
|
|
284
291
|
});
|
|
285
292
|
transformedResult = mcpResult.content[0].text;
|
|
286
293
|
}
|
|
@@ -295,20 +302,20 @@ class RestApiRouter {
|
|
|
295
302
|
data: {
|
|
296
303
|
result: transformedResult,
|
|
297
304
|
tool: toolName,
|
|
298
|
-
executionTime
|
|
305
|
+
executionTime,
|
|
299
306
|
},
|
|
300
307
|
meta: {
|
|
301
308
|
timestamp: new Date().toISOString(),
|
|
302
309
|
requestId,
|
|
303
|
-
version: this.config.version
|
|
304
|
-
}
|
|
310
|
+
version: this.config.version,
|
|
311
|
+
},
|
|
305
312
|
};
|
|
306
313
|
await this.sendJsonResponse(res, HttpStatus.OK, response);
|
|
307
314
|
this.logger.info('Tool execution completed', {
|
|
308
315
|
requestId,
|
|
309
316
|
toolName,
|
|
310
317
|
executionTime,
|
|
311
|
-
success: true
|
|
318
|
+
success: true,
|
|
312
319
|
});
|
|
313
320
|
}
|
|
314
321
|
catch (error) {
|
|
@@ -316,7 +323,7 @@ class RestApiRouter {
|
|
|
316
323
|
this.logger.error('Tool execution failed', error instanceof Error ? error : new Error(String(error)), {
|
|
317
324
|
requestId,
|
|
318
325
|
toolName,
|
|
319
|
-
errorMessage
|
|
326
|
+
errorMessage,
|
|
320
327
|
});
|
|
321
328
|
await this.sendErrorResponse(res, requestId, HttpStatus.INTERNAL_SERVER_ERROR, 'EXECUTION_ERROR', errorMessage);
|
|
322
329
|
}
|
|
@@ -332,13 +339,13 @@ class RestApiRouter {
|
|
|
332
339
|
this.logger.info('OpenAPI specification served successfully', {
|
|
333
340
|
requestId,
|
|
334
341
|
pathCount: Object.keys(spec.paths).length,
|
|
335
|
-
componentCount: Object.keys(spec.components?.schemas || {}).length
|
|
342
|
+
componentCount: Object.keys(spec.components?.schemas || {}).length,
|
|
336
343
|
});
|
|
337
344
|
}
|
|
338
345
|
catch (error) {
|
|
339
346
|
this.logger.error('Failed to generate OpenAPI specification', error instanceof Error ? error : new Error(String(error)), {
|
|
340
347
|
requestId,
|
|
341
|
-
errorMessage: error instanceof Error ? error.message : String(error)
|
|
348
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
342
349
|
});
|
|
343
350
|
await this.sendErrorResponse(res, requestId, HttpStatus.INTERNAL_SERVER_ERROR, 'OPENAPI_ERROR', 'Failed to generate OpenAPI specification');
|
|
344
351
|
}
|
|
@@ -360,10 +367,13 @@ class RestApiRouter {
|
|
|
360
367
|
let httpStatus = HttpStatus.OK;
|
|
361
368
|
if (!response.success) {
|
|
362
369
|
const errorCode = response.error?.code;
|
|
363
|
-
if (errorCode === 'VECTOR_DB_UNAVAILABLE' ||
|
|
370
|
+
if (errorCode === 'VECTOR_DB_UNAVAILABLE' ||
|
|
371
|
+
errorCode === 'HEALTH_CHECK_FAILED') {
|
|
364
372
|
httpStatus = HttpStatus.SERVICE_UNAVAILABLE;
|
|
365
373
|
}
|
|
366
|
-
else if (errorCode === 'SERVICE_INIT_FAILED' ||
|
|
374
|
+
else if (errorCode === 'SERVICE_INIT_FAILED' ||
|
|
375
|
+
errorCode === 'COLLECTION_INIT_FAILED' ||
|
|
376
|
+
errorCode === 'RESYNC_FAILED') {
|
|
367
377
|
httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
|
|
368
378
|
}
|
|
369
379
|
else {
|
|
@@ -375,14 +385,14 @@ class RestApiRouter {
|
|
|
375
385
|
requestId,
|
|
376
386
|
success: response.success,
|
|
377
387
|
upserted: response.data?.upserted,
|
|
378
|
-
deleted: response.data?.deleted
|
|
388
|
+
deleted: response.data?.deleted,
|
|
379
389
|
});
|
|
380
390
|
}
|
|
381
391
|
catch (error) {
|
|
382
392
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
383
393
|
this.logger.error('Resource sync request failed', error instanceof Error ? error : new Error(String(error)), {
|
|
384
394
|
requestId,
|
|
385
|
-
errorMessage
|
|
395
|
+
errorMessage,
|
|
386
396
|
});
|
|
387
397
|
await this.sendErrorResponse(res, requestId, HttpStatus.INTERNAL_SERVER_ERROR, 'SYNC_ERROR', 'Resource sync failed', { error: errorMessage });
|
|
388
398
|
}
|
|
@@ -395,30 +405,33 @@ class RestApiRouter {
|
|
|
395
405
|
async handleGetResourceKinds(req, res, requestId, searchParams) {
|
|
396
406
|
try {
|
|
397
407
|
const namespace = searchParams.get('namespace') || undefined;
|
|
398
|
-
this.logger.info('Processing get resource kinds request', {
|
|
408
|
+
this.logger.info('Processing get resource kinds request', {
|
|
409
|
+
requestId,
|
|
410
|
+
namespace,
|
|
411
|
+
});
|
|
399
412
|
const kinds = await (0, resource_tools_1.getResourceKinds)(namespace);
|
|
400
413
|
const response = {
|
|
401
414
|
success: true,
|
|
402
415
|
data: {
|
|
403
|
-
kinds
|
|
416
|
+
kinds,
|
|
404
417
|
},
|
|
405
418
|
meta: {
|
|
406
419
|
timestamp: new Date().toISOString(),
|
|
407
420
|
requestId,
|
|
408
|
-
version: this.config.version
|
|
409
|
-
}
|
|
421
|
+
version: this.config.version,
|
|
422
|
+
},
|
|
410
423
|
};
|
|
411
424
|
await this.sendJsonResponse(res, HttpStatus.OK, response);
|
|
412
425
|
this.logger.info('Get resource kinds request completed', {
|
|
413
426
|
requestId,
|
|
414
|
-
kindCount: kinds.length
|
|
427
|
+
kindCount: kinds.length,
|
|
415
428
|
});
|
|
416
429
|
}
|
|
417
430
|
catch (error) {
|
|
418
431
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
419
432
|
this.logger.error('Get resource kinds request failed', error instanceof Error ? error : new Error(String(error)), {
|
|
420
433
|
requestId,
|
|
421
|
-
errorMessage
|
|
434
|
+
errorMessage,
|
|
422
435
|
});
|
|
423
436
|
await this.sendErrorResponse(res, requestId, HttpStatus.INTERNAL_SERVER_ERROR, 'RESOURCE_KINDS_ERROR', 'Failed to retrieve resource kinds', { error: errorMessage });
|
|
424
437
|
}
|
|
@@ -454,7 +467,8 @@ class RestApiRouter {
|
|
|
454
467
|
await this.sendErrorResponse(res, requestId, HttpStatus.BAD_REQUEST, 'INVALID_PARAMETER', 'The "offset" parameter must be a non-negative integer');
|
|
455
468
|
return;
|
|
456
469
|
}
|
|
457
|
-
if (minScoreParam &&
|
|
470
|
+
if (minScoreParam &&
|
|
471
|
+
(isNaN(minScore) || minScore < 0 || minScore > 1)) {
|
|
458
472
|
await this.sendErrorResponse(res, requestId, HttpStatus.BAD_REQUEST, 'INVALID_PARAMETER', 'The "minScore" parameter must be a number between 0 and 1');
|
|
459
473
|
return;
|
|
460
474
|
}
|
|
@@ -466,7 +480,7 @@ class RestApiRouter {
|
|
|
466
480
|
apiVersion,
|
|
467
481
|
limit,
|
|
468
482
|
offset,
|
|
469
|
-
minScore
|
|
483
|
+
minScore,
|
|
470
484
|
});
|
|
471
485
|
// Build filters
|
|
472
486
|
const filters = {};
|
|
@@ -492,7 +506,7 @@ class RestApiRouter {
|
|
|
492
506
|
apiVersion: r.resource.apiVersion,
|
|
493
507
|
labels: r.resource.labels || {},
|
|
494
508
|
createdAt: r.resource.createdAt,
|
|
495
|
-
score: r.score
|
|
509
|
+
score: r.score,
|
|
496
510
|
}));
|
|
497
511
|
const response = {
|
|
498
512
|
success: true,
|
|
@@ -500,27 +514,27 @@ class RestApiRouter {
|
|
|
500
514
|
resources,
|
|
501
515
|
total: results.length,
|
|
502
516
|
limit,
|
|
503
|
-
offset
|
|
517
|
+
offset,
|
|
504
518
|
},
|
|
505
519
|
meta: {
|
|
506
520
|
timestamp: new Date().toISOString(),
|
|
507
521
|
requestId,
|
|
508
|
-
version: this.config.version
|
|
509
|
-
}
|
|
522
|
+
version: this.config.version,
|
|
523
|
+
},
|
|
510
524
|
};
|
|
511
525
|
await this.sendJsonResponse(res, HttpStatus.OK, response);
|
|
512
526
|
this.logger.info('Search resources request completed', {
|
|
513
527
|
requestId,
|
|
514
528
|
query: q,
|
|
515
529
|
resultCount: resources.length,
|
|
516
|
-
totalMatches: results.length
|
|
530
|
+
totalMatches: results.length,
|
|
517
531
|
});
|
|
518
532
|
}
|
|
519
533
|
catch (error) {
|
|
520
534
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
521
535
|
this.logger.error('Search resources request failed', error instanceof Error ? error : new Error(String(error)), {
|
|
522
536
|
requestId,
|
|
523
|
-
errorMessage
|
|
537
|
+
errorMessage,
|
|
524
538
|
});
|
|
525
539
|
await this.sendErrorResponse(res, requestId, HttpStatus.INTERNAL_SERVER_ERROR, 'SEARCH_ERROR', 'Failed to search resources', { error: errorMessage });
|
|
526
540
|
}
|
|
@@ -567,14 +581,22 @@ class RestApiRouter {
|
|
|
567
581
|
namespace,
|
|
568
582
|
includeStatus,
|
|
569
583
|
limit,
|
|
570
|
-
offset
|
|
584
|
+
offset,
|
|
571
585
|
});
|
|
572
586
|
// PRD #343: Never pass includeStatus to listResources (it uses direct kubectl)
|
|
573
587
|
// Fetch status via plugin separately if requested
|
|
574
|
-
const result = await (0, resource_tools_1.listResources)({
|
|
588
|
+
const result = await (0, resource_tools_1.listResources)({
|
|
589
|
+
kind,
|
|
590
|
+
apiVersion,
|
|
591
|
+
namespace,
|
|
592
|
+
limit,
|
|
593
|
+
offset,
|
|
594
|
+
});
|
|
575
595
|
// Enrich with live status via plugin if requested
|
|
576
596
|
// PRD #359: Use unified plugin registry
|
|
577
|
-
if (includeStatus &&
|
|
597
|
+
if (includeStatus &&
|
|
598
|
+
result.resources.length > 0 &&
|
|
599
|
+
(0, plugin_registry_1.isPluginInitialized)()) {
|
|
578
600
|
const statusPromises = result.resources.map(async (resource) => {
|
|
579
601
|
const resourceType = resource.apiGroup
|
|
580
602
|
? `${resource.kind.toLowerCase()}.${resource.apiGroup}`
|
|
@@ -583,7 +605,7 @@ class RestApiRouter {
|
|
|
583
605
|
const pluginResponse = await (0, plugin_registry_1.invokePluginTool)('agentic-tools', 'kubectl_get_resource_json', {
|
|
584
606
|
resource: resourceId,
|
|
585
607
|
namespace: resource.namespace,
|
|
586
|
-
field: 'status'
|
|
608
|
+
field: 'status',
|
|
587
609
|
});
|
|
588
610
|
if (pluginResponse.success && pluginResponse.result) {
|
|
589
611
|
const pluginResult = pluginResponse.result;
|
|
@@ -606,22 +628,22 @@ class RestApiRouter {
|
|
|
606
628
|
meta: {
|
|
607
629
|
timestamp: new Date().toISOString(),
|
|
608
630
|
requestId,
|
|
609
|
-
version: this.config.version
|
|
610
|
-
}
|
|
631
|
+
version: this.config.version,
|
|
632
|
+
},
|
|
611
633
|
};
|
|
612
634
|
await this.sendJsonResponse(res, HttpStatus.OK, response);
|
|
613
635
|
this.logger.info('List resources request completed', {
|
|
614
636
|
requestId,
|
|
615
637
|
resourceCount: result.resources.length,
|
|
616
638
|
total: result.total,
|
|
617
|
-
includeStatus
|
|
639
|
+
includeStatus,
|
|
618
640
|
});
|
|
619
641
|
}
|
|
620
642
|
catch (error) {
|
|
621
643
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
622
644
|
this.logger.error('List resources request failed', error instanceof Error ? error : new Error(String(error)), {
|
|
623
645
|
requestId,
|
|
624
|
-
errorMessage
|
|
646
|
+
errorMessage,
|
|
625
647
|
});
|
|
626
648
|
await this.sendErrorResponse(res, requestId, HttpStatus.INTERNAL_SERVER_ERROR, 'LIST_RESOURCES_ERROR', 'Failed to list resources', { error: errorMessage });
|
|
627
649
|
}
|
|
@@ -637,25 +659,25 @@ class RestApiRouter {
|
|
|
637
659
|
const response = {
|
|
638
660
|
success: true,
|
|
639
661
|
data: {
|
|
640
|
-
namespaces
|
|
662
|
+
namespaces,
|
|
641
663
|
},
|
|
642
664
|
meta: {
|
|
643
665
|
timestamp: new Date().toISOString(),
|
|
644
666
|
requestId,
|
|
645
|
-
version: this.config.version
|
|
646
|
-
}
|
|
667
|
+
version: this.config.version,
|
|
668
|
+
},
|
|
647
669
|
};
|
|
648
670
|
await this.sendJsonResponse(res, HttpStatus.OK, response);
|
|
649
671
|
this.logger.info('Get namespaces request completed', {
|
|
650
672
|
requestId,
|
|
651
|
-
namespaceCount: namespaces.length
|
|
673
|
+
namespaceCount: namespaces.length,
|
|
652
674
|
});
|
|
653
675
|
}
|
|
654
676
|
catch (error) {
|
|
655
677
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
656
678
|
this.logger.error('Get namespaces request failed', error instanceof Error ? error : new Error(String(error)), {
|
|
657
679
|
requestId,
|
|
658
|
-
errorMessage
|
|
680
|
+
errorMessage,
|
|
659
681
|
});
|
|
660
682
|
await this.sendErrorResponse(res, requestId, HttpStatus.INTERNAL_SERVER_ERROR, 'NAMESPACES_ERROR', 'Failed to retrieve namespaces', { error: errorMessage });
|
|
661
683
|
}
|
|
@@ -683,7 +705,13 @@ class RestApiRouter {
|
|
|
683
705
|
await this.sendErrorResponse(res, requestId, HttpStatus.BAD_REQUEST, 'BAD_REQUEST', 'name query parameter is required');
|
|
684
706
|
return;
|
|
685
707
|
}
|
|
686
|
-
this.logger.info('Processing get resource request', {
|
|
708
|
+
this.logger.info('Processing get resource request', {
|
|
709
|
+
requestId,
|
|
710
|
+
kind,
|
|
711
|
+
apiVersion,
|
|
712
|
+
name,
|
|
713
|
+
namespace,
|
|
714
|
+
});
|
|
687
715
|
// Extract apiGroup from apiVersion (e.g., "apps/v1" -> "apps", "v1" -> "")
|
|
688
716
|
const apiGroup = apiVersion.includes('/') ? apiVersion.split('/')[0] : '';
|
|
689
717
|
// PRD #359: Use unified plugin registry
|
|
@@ -692,12 +720,14 @@ class RestApiRouter {
|
|
|
692
720
|
return;
|
|
693
721
|
}
|
|
694
722
|
// Build resource identifier (kind.group/name or kind/name for core resources)
|
|
695
|
-
const resourceType = apiGroup
|
|
723
|
+
const resourceType = apiGroup
|
|
724
|
+
? `${kind.toLowerCase()}.${apiGroup}`
|
|
725
|
+
: kind.toLowerCase();
|
|
696
726
|
const resourceId = `${resourceType}/${name}`;
|
|
697
727
|
// PRD #359: Use unified plugin registry for kubectl operations
|
|
698
728
|
const pluginResponse = await (0, plugin_registry_1.invokePluginTool)('agentic-tools', 'kubectl_get_resource_json', {
|
|
699
729
|
resource: resourceId,
|
|
700
|
-
namespace: namespace
|
|
730
|
+
namespace: namespace,
|
|
701
731
|
});
|
|
702
732
|
// Check for plugin-level failures first
|
|
703
733
|
if (!pluginResponse.success) {
|
|
@@ -734,27 +764,27 @@ class RestApiRouter {
|
|
|
734
764
|
const response = {
|
|
735
765
|
success: true,
|
|
736
766
|
data: {
|
|
737
|
-
resource
|
|
767
|
+
resource,
|
|
738
768
|
},
|
|
739
769
|
meta: {
|
|
740
770
|
timestamp: new Date().toISOString(),
|
|
741
771
|
requestId,
|
|
742
|
-
version: this.config.version
|
|
743
|
-
}
|
|
772
|
+
version: this.config.version,
|
|
773
|
+
},
|
|
744
774
|
};
|
|
745
775
|
await this.sendJsonResponse(res, HttpStatus.OK, response);
|
|
746
776
|
this.logger.info('Get resource request completed', {
|
|
747
777
|
requestId,
|
|
748
778
|
kind,
|
|
749
779
|
name,
|
|
750
|
-
namespace
|
|
780
|
+
namespace,
|
|
751
781
|
});
|
|
752
782
|
}
|
|
753
783
|
catch (error) {
|
|
754
784
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
755
785
|
this.logger.error('Get resource request failed', error instanceof Error ? error : new Error(String(error)), {
|
|
756
786
|
requestId,
|
|
757
|
-
errorMessage
|
|
787
|
+
errorMessage,
|
|
758
788
|
});
|
|
759
789
|
await this.sendErrorResponse(res, requestId, HttpStatus.INTERNAL_SERVER_ERROR, 'RESOURCE_ERROR', 'Failed to retrieve resource', { error: errorMessage });
|
|
760
790
|
}
|
|
@@ -778,7 +808,13 @@ class RestApiRouter {
|
|
|
778
808
|
await this.sendErrorResponse(res, requestId, HttpStatus.BAD_REQUEST, 'BAD_REQUEST', 'kind query parameter is required');
|
|
779
809
|
return;
|
|
780
810
|
}
|
|
781
|
-
this.logger.info('Processing get events request', {
|
|
811
|
+
this.logger.info('Processing get events request', {
|
|
812
|
+
requestId,
|
|
813
|
+
name,
|
|
814
|
+
kind,
|
|
815
|
+
namespace,
|
|
816
|
+
uid,
|
|
817
|
+
});
|
|
782
818
|
// PRD #359: Use unified plugin registry
|
|
783
819
|
if (!(0, plugin_registry_1.isPluginInitialized)()) {
|
|
784
820
|
await this.sendErrorResponse(res, requestId, HttpStatus.SERVICE_UNAVAILABLE, 'PLUGIN_UNAVAILABLE', 'Plugin system not initialized');
|
|
@@ -787,7 +823,7 @@ class RestApiRouter {
|
|
|
787
823
|
// Build field selector for involvedObject filtering
|
|
788
824
|
const fieldSelectors = [
|
|
789
825
|
`involvedObject.name=${name}`,
|
|
790
|
-
`involvedObject.kind=${kind}
|
|
826
|
+
`involvedObject.kind=${kind}`,
|
|
791
827
|
];
|
|
792
828
|
if (uid) {
|
|
793
829
|
fieldSelectors.push(`involvedObject.uid=${uid}`);
|
|
@@ -795,7 +831,7 @@ class RestApiRouter {
|
|
|
795
831
|
// PRD #359: Use unified plugin registry for kubectl operations
|
|
796
832
|
const pluginResponse = await (0, plugin_registry_1.invokePluginTool)('agentic-tools', 'kubectl_events', {
|
|
797
833
|
namespace: namespace,
|
|
798
|
-
args: [`--field-selector=${fieldSelectors.join(',')}`]
|
|
834
|
+
args: [`--field-selector=${fieldSelectors.join(',')}`],
|
|
799
835
|
});
|
|
800
836
|
const events = [];
|
|
801
837
|
if (pluginResponse.success && pluginResponse.result) {
|
|
@@ -803,7 +839,9 @@ class RestApiRouter {
|
|
|
803
839
|
if (pluginResult.success && pluginResult.data) {
|
|
804
840
|
// Parse the table output or handle JSON if available
|
|
805
841
|
// Events output is typically table format, so we need to parse it
|
|
806
|
-
const lines = pluginResult.data
|
|
842
|
+
const lines = pluginResult.data
|
|
843
|
+
.split('\n')
|
|
844
|
+
.filter(line => line.trim());
|
|
807
845
|
if (lines.length > 1) {
|
|
808
846
|
// Skip header line, parse remaining lines
|
|
809
847
|
for (let i = 1; i < lines.length; i++) {
|
|
@@ -814,7 +852,7 @@ class RestApiRouter {
|
|
|
814
852
|
type: parts[1],
|
|
815
853
|
reason: parts[2],
|
|
816
854
|
involvedObject: { kind, name },
|
|
817
|
-
message: parts.slice(4).join(' ')
|
|
855
|
+
message: parts.slice(4).join(' '),
|
|
818
856
|
});
|
|
819
857
|
}
|
|
820
858
|
}
|
|
@@ -825,13 +863,13 @@ class RestApiRouter {
|
|
|
825
863
|
success: true,
|
|
826
864
|
data: {
|
|
827
865
|
events,
|
|
828
|
-
count: events.length
|
|
866
|
+
count: events.length,
|
|
829
867
|
},
|
|
830
868
|
meta: {
|
|
831
869
|
timestamp: new Date().toISOString(),
|
|
832
870
|
requestId,
|
|
833
|
-
version: this.config.version
|
|
834
|
-
}
|
|
871
|
+
version: this.config.version,
|
|
872
|
+
},
|
|
835
873
|
};
|
|
836
874
|
await this.sendJsonResponse(res, HttpStatus.OK, response);
|
|
837
875
|
this.logger.info('Get events request completed', {
|
|
@@ -839,14 +877,14 @@ class RestApiRouter {
|
|
|
839
877
|
name,
|
|
840
878
|
kind,
|
|
841
879
|
namespace,
|
|
842
|
-
eventCount: events.length
|
|
880
|
+
eventCount: events.length,
|
|
843
881
|
});
|
|
844
882
|
}
|
|
845
883
|
catch (error) {
|
|
846
884
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
847
885
|
this.logger.error('Get events request failed', error instanceof Error ? error : new Error(String(error)), {
|
|
848
886
|
requestId,
|
|
849
|
-
errorMessage
|
|
887
|
+
errorMessage,
|
|
850
888
|
});
|
|
851
889
|
await this.sendErrorResponse(res, requestId, HttpStatus.INTERNAL_SERVER_ERROR, 'EVENTS_ERROR', 'Failed to retrieve events', { error: errorMessage });
|
|
852
890
|
}
|
|
@@ -879,7 +917,13 @@ class RestApiRouter {
|
|
|
879
917
|
return;
|
|
880
918
|
}
|
|
881
919
|
}
|
|
882
|
-
this.logger.info('Processing get logs request', {
|
|
920
|
+
this.logger.info('Processing get logs request', {
|
|
921
|
+
requestId,
|
|
922
|
+
name,
|
|
923
|
+
namespace,
|
|
924
|
+
container,
|
|
925
|
+
tailLines,
|
|
926
|
+
});
|
|
883
927
|
// PRD #359: Use unified plugin registry
|
|
884
928
|
if (!(0, plugin_registry_1.isPluginInitialized)()) {
|
|
885
929
|
await this.sendErrorResponse(res, requestId, HttpStatus.SERVICE_UNAVAILABLE, 'PLUGIN_UNAVAILABLE', 'Plugin system not initialized');
|
|
@@ -897,7 +941,7 @@ class RestApiRouter {
|
|
|
897
941
|
const pluginResponse = await (0, plugin_registry_1.invokePluginTool)('agentic-tools', 'kubectl_logs', {
|
|
898
942
|
resource: name,
|
|
899
943
|
namespace: namespace,
|
|
900
|
-
args: args.length > 0 ? args : undefined
|
|
944
|
+
args: args.length > 0 ? args : undefined,
|
|
901
945
|
});
|
|
902
946
|
if (!pluginResponse.success) {
|
|
903
947
|
const errorMsg = pluginResponse.error?.message || 'Failed to retrieve logs';
|
|
@@ -914,13 +958,13 @@ class RestApiRouter {
|
|
|
914
958
|
data: {
|
|
915
959
|
logs: result.data,
|
|
916
960
|
container: container || 'default',
|
|
917
|
-
containerCount: 1
|
|
961
|
+
containerCount: 1,
|
|
918
962
|
},
|
|
919
963
|
meta: {
|
|
920
964
|
timestamp: new Date().toISOString(),
|
|
921
965
|
requestId,
|
|
922
|
-
version: this.config.version
|
|
923
|
-
}
|
|
966
|
+
version: this.config.version,
|
|
967
|
+
},
|
|
924
968
|
};
|
|
925
969
|
await this.sendJsonResponse(res, HttpStatus.OK, response);
|
|
926
970
|
this.logger.info('Get logs request completed', {
|
|
@@ -928,14 +972,14 @@ class RestApiRouter {
|
|
|
928
972
|
name,
|
|
929
973
|
namespace,
|
|
930
974
|
container: container || 'default',
|
|
931
|
-
logLength: result.data.length
|
|
975
|
+
logLength: result.data.length,
|
|
932
976
|
});
|
|
933
977
|
}
|
|
934
978
|
catch (error) {
|
|
935
979
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
936
980
|
this.logger.error('Get logs request failed', error instanceof Error ? error : new Error(String(error)), {
|
|
937
981
|
requestId,
|
|
938
|
-
errorMessage
|
|
982
|
+
errorMessage,
|
|
939
983
|
});
|
|
940
984
|
await this.sendErrorResponse(res, requestId, HttpStatus.INTERNAL_SERVER_ERROR, 'LOGS_ERROR', 'Failed to retrieve logs', { error: errorMessage });
|
|
941
985
|
}
|
|
@@ -953,18 +997,18 @@ class RestApiRouter {
|
|
|
953
997
|
meta: {
|
|
954
998
|
timestamp: new Date().toISOString(),
|
|
955
999
|
requestId,
|
|
956
|
-
version: this.config.version
|
|
957
|
-
}
|
|
1000
|
+
version: this.config.version,
|
|
1001
|
+
},
|
|
958
1002
|
};
|
|
959
1003
|
await this.sendJsonResponse(res, HttpStatus.OK, response);
|
|
960
1004
|
this.logger.info('Prompts list request completed', {
|
|
961
1005
|
requestId,
|
|
962
|
-
promptCount: result.prompts?.length || 0
|
|
1006
|
+
promptCount: result.prompts?.length || 0,
|
|
963
1007
|
});
|
|
964
1008
|
}
|
|
965
1009
|
catch (error) {
|
|
966
1010
|
this.logger.error('Prompts list request failed', error instanceof Error ? error : new Error(String(error)), {
|
|
967
|
-
requestId
|
|
1011
|
+
requestId,
|
|
968
1012
|
});
|
|
969
1013
|
await this.sendErrorResponse(res, requestId, HttpStatus.INTERNAL_SERVER_ERROR, 'PROMPTS_LIST_ERROR', 'Failed to list prompts');
|
|
970
1014
|
}
|
|
@@ -974,7 +1018,10 @@ class RestApiRouter {
|
|
|
974
1018
|
*/
|
|
975
1019
|
async handlePromptsGetRequest(req, res, requestId, promptName, body) {
|
|
976
1020
|
try {
|
|
977
|
-
this.logger.info('Processing prompt get request', {
|
|
1021
|
+
this.logger.info('Processing prompt get request', {
|
|
1022
|
+
requestId,
|
|
1023
|
+
promptName,
|
|
1024
|
+
});
|
|
978
1025
|
const bodyObj = body;
|
|
979
1026
|
const result = await (0, prompts_1.handlePromptsGetRequest)({ name: promptName, arguments: bodyObj?.arguments }, this.logger, requestId);
|
|
980
1027
|
const response = {
|
|
@@ -983,25 +1030,27 @@ class RestApiRouter {
|
|
|
983
1030
|
meta: {
|
|
984
1031
|
timestamp: new Date().toISOString(),
|
|
985
1032
|
requestId,
|
|
986
|
-
version: this.config.version
|
|
987
|
-
}
|
|
1033
|
+
version: this.config.version,
|
|
1034
|
+
},
|
|
988
1035
|
};
|
|
989
1036
|
await this.sendJsonResponse(res, HttpStatus.OK, response);
|
|
990
1037
|
this.logger.info('Prompt get request completed', {
|
|
991
1038
|
requestId,
|
|
992
|
-
promptName
|
|
1039
|
+
promptName,
|
|
993
1040
|
});
|
|
994
1041
|
}
|
|
995
1042
|
catch (error) {
|
|
996
1043
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
997
1044
|
this.logger.error('Prompt get request failed', error instanceof Error ? error : new Error(String(error)), {
|
|
998
1045
|
requestId,
|
|
999
|
-
promptName
|
|
1046
|
+
promptName,
|
|
1000
1047
|
});
|
|
1001
1048
|
// Check if it's a validation error (missing required arguments or prompt not found)
|
|
1002
1049
|
const isValidationError = errorMessage.includes('Missing required arguments') ||
|
|
1003
1050
|
errorMessage.includes('Prompt not found');
|
|
1004
|
-
await this.sendErrorResponse(res, requestId, isValidationError
|
|
1051
|
+
await this.sendErrorResponse(res, requestId, isValidationError
|
|
1052
|
+
? HttpStatus.BAD_REQUEST
|
|
1053
|
+
: HttpStatus.INTERNAL_SERVER_ERROR, isValidationError ? 'VALIDATION_ERROR' : 'PROMPT_GET_ERROR', errorMessage);
|
|
1005
1054
|
}
|
|
1006
1055
|
}
|
|
1007
1056
|
/**
|
|
@@ -1020,7 +1069,7 @@ class RestApiRouter {
|
|
|
1020
1069
|
requestId,
|
|
1021
1070
|
sessionIds,
|
|
1022
1071
|
isMultiSession,
|
|
1023
|
-
reload
|
|
1072
|
+
reload,
|
|
1024
1073
|
});
|
|
1025
1074
|
// Fetch all sessions
|
|
1026
1075
|
const sessions = [];
|
|
@@ -1037,11 +1086,13 @@ class RestApiRouter {
|
|
|
1037
1086
|
// For single session, check cache (multi-session doesn't use cache yet)
|
|
1038
1087
|
// PRD #320: Skip cache if reload=true to regenerate from current session data
|
|
1039
1088
|
const primarySession = sessions[0];
|
|
1040
|
-
if (!isMultiSession &&
|
|
1089
|
+
if (!isMultiSession &&
|
|
1090
|
+
!reload &&
|
|
1091
|
+
primarySession.data.cachedVisualization) {
|
|
1041
1092
|
this.logger.info('Returning cached visualization', {
|
|
1042
1093
|
requestId,
|
|
1043
1094
|
sessionId: sessionIds[0],
|
|
1044
|
-
generatedAt: primarySession.data.cachedVisualization.generatedAt
|
|
1095
|
+
generatedAt: primarySession.data.cachedVisualization.generatedAt,
|
|
1045
1096
|
});
|
|
1046
1097
|
const cachedResponse = {
|
|
1047
1098
|
success: true,
|
|
@@ -1049,13 +1100,13 @@ class RestApiRouter {
|
|
|
1049
1100
|
title: primarySession.data.cachedVisualization.title,
|
|
1050
1101
|
visualizations: primarySession.data.cachedVisualization.visualizations,
|
|
1051
1102
|
insights: primarySession.data.cachedVisualization.insights,
|
|
1052
|
-
toolsUsed: primarySession.data.cachedVisualization.toolsUsed // PRD #320
|
|
1103
|
+
toolsUsed: primarySession.data.cachedVisualization.toolsUsed, // PRD #320
|
|
1053
1104
|
},
|
|
1054
1105
|
meta: {
|
|
1055
1106
|
timestamp: new Date().toISOString(),
|
|
1056
1107
|
requestId,
|
|
1057
|
-
version: this.config.version
|
|
1058
|
-
}
|
|
1108
|
+
version: this.config.version,
|
|
1109
|
+
},
|
|
1059
1110
|
};
|
|
1060
1111
|
await this.sendJsonResponse(res, HttpStatus.OK, cachedResponse);
|
|
1061
1112
|
return;
|
|
@@ -1069,7 +1120,12 @@ class RestApiRouter {
|
|
|
1069
1120
|
// PRD #320: Select prompt based on tool name (defaults to 'query' for backwards compatibility)
|
|
1070
1121
|
const toolName = (primarySession.data.toolName || 'query');
|
|
1071
1122
|
const promptName = (0, visualization_1.getPromptForTool)(toolName);
|
|
1072
|
-
this.logger.info('Loading visualization prompt', {
|
|
1123
|
+
this.logger.info('Loading visualization prompt', {
|
|
1124
|
+
requestId,
|
|
1125
|
+
sessionIds,
|
|
1126
|
+
toolName,
|
|
1127
|
+
promptName,
|
|
1128
|
+
});
|
|
1073
1129
|
// Load system prompt with session context
|
|
1074
1130
|
// PRD #320: Unified visualization prompt with tool-aware data selection
|
|
1075
1131
|
let intent;
|
|
@@ -1079,7 +1135,9 @@ class RestApiRouter {
|
|
|
1079
1135
|
switch (toolName) {
|
|
1080
1136
|
case 'recommend':
|
|
1081
1137
|
intent = sessionData.intent || '';
|
|
1082
|
-
data = isMultiSession
|
|
1138
|
+
data = isMultiSession
|
|
1139
|
+
? sessions.map(s => s.data)
|
|
1140
|
+
: primarySession.data;
|
|
1083
1141
|
break;
|
|
1084
1142
|
case 'remediate':
|
|
1085
1143
|
intent = sessionData.issue || '';
|
|
@@ -1102,15 +1160,17 @@ class RestApiRouter {
|
|
|
1102
1160
|
const promptData = {
|
|
1103
1161
|
intent,
|
|
1104
1162
|
data: JSON.stringify(data, null, 2),
|
|
1105
|
-
visualizationOutput: (0, shared_prompt_loader_1.loadPrompt)('partials/visualization-output')
|
|
1163
|
+
visualizationOutput: (0, shared_prompt_loader_1.loadPrompt)('partials/visualization-output'),
|
|
1106
1164
|
};
|
|
1107
1165
|
const systemPrompt = (0, shared_prompt_loader_1.loadPrompt)(promptName, promptData);
|
|
1108
1166
|
// PRD #343: Local executor for non-plugin tools (capability, resource, mermaid)
|
|
1109
1167
|
const localToolExecutor = async (toolName, input) => {
|
|
1110
|
-
if (toolName.startsWith('search_capabilities') ||
|
|
1168
|
+
if (toolName.startsWith('search_capabilities') ||
|
|
1169
|
+
toolName.startsWith('query_capabilities')) {
|
|
1111
1170
|
return (0, capability_tools_1.executeCapabilityTools)(toolName, input);
|
|
1112
1171
|
}
|
|
1113
|
-
if (toolName.startsWith('search_resources') ||
|
|
1172
|
+
if (toolName.startsWith('search_resources') ||
|
|
1173
|
+
toolName.startsWith('query_resources')) {
|
|
1114
1174
|
return (0, resource_tools_1.executeResourceTools)(toolName, input);
|
|
1115
1175
|
}
|
|
1116
1176
|
// PRD #320: Mermaid validation tools
|
|
@@ -1120,7 +1180,7 @@ class RestApiRouter {
|
|
|
1120
1180
|
return {
|
|
1121
1181
|
success: false,
|
|
1122
1182
|
error: `Unknown tool: ${toolName}`,
|
|
1123
|
-
message: `Tool '${toolName}' is not implemented in visualization
|
|
1183
|
+
message: `Tool '${toolName}' is not implemented in visualization`,
|
|
1124
1184
|
};
|
|
1125
1185
|
};
|
|
1126
1186
|
// PRD #343: Use plugin executor for kubectl tools, local for others
|
|
@@ -1134,42 +1194,57 @@ class RestApiRouter {
|
|
|
1134
1194
|
'kubectl_describe',
|
|
1135
1195
|
'kubectl_logs',
|
|
1136
1196
|
'kubectl_events',
|
|
1137
|
-
'kubectl_get_crd_schema'
|
|
1197
|
+
'kubectl_get_crd_schema',
|
|
1138
1198
|
];
|
|
1139
1199
|
const pluginKubectlTools = this.pluginManager
|
|
1140
|
-
? this.pluginManager
|
|
1200
|
+
? this.pluginManager
|
|
1201
|
+
.getDiscoveredTools()
|
|
1202
|
+
.filter(t => KUBECTL_READONLY_TOOL_NAMES.includes(t.name))
|
|
1141
1203
|
: [];
|
|
1142
|
-
this.logger.info('Starting AI visualization generation with tools', {
|
|
1204
|
+
this.logger.info('Starting AI visualization generation with tools', {
|
|
1205
|
+
requestId,
|
|
1206
|
+
sessionIds,
|
|
1207
|
+
toolName,
|
|
1208
|
+
});
|
|
1143
1209
|
// Execute tool loop - AI can gather additional data if needed
|
|
1144
1210
|
const result = await aiProvider.toolLoop({
|
|
1145
1211
|
systemPrompt,
|
|
1146
1212
|
userMessage: 'Generate visualizations based on the query results provided. Use tools if you need additional information about any resources.',
|
|
1147
1213
|
// PRD #320: Include MERMAID_TOOLS for diagram validation
|
|
1148
1214
|
// PRD #343: kubectl tools from plugin
|
|
1149
|
-
tools: [
|
|
1215
|
+
tools: [
|
|
1216
|
+
...capability_tools_1.CAPABILITY_TOOLS,
|
|
1217
|
+
...resource_tools_1.RESOURCE_TOOLS,
|
|
1218
|
+
...pluginKubectlTools,
|
|
1219
|
+
...mermaid_tools_1.MERMAID_TOOLS,
|
|
1220
|
+
],
|
|
1150
1221
|
toolExecutor: executeVisualizationTools,
|
|
1151
1222
|
maxIterations: 10, // Allow enough iterations for tool calls + JSON generation
|
|
1152
|
-
operation: `visualize-${toolName}
|
|
1223
|
+
operation: `visualize-${toolName}`, // PRD #320: Include tool name for debugging
|
|
1153
1224
|
});
|
|
1154
1225
|
this.logger.info('AI visualization generation completed', {
|
|
1155
1226
|
requestId,
|
|
1156
1227
|
sessionIds,
|
|
1157
1228
|
toolName,
|
|
1158
1229
|
iterations: result.iterations,
|
|
1159
|
-
toolsUsed: [...new Set(result.toolCallsExecuted.map(tc => tc.tool))]
|
|
1230
|
+
toolsUsed: [...new Set(result.toolCallsExecuted.map(tc => tc.tool))],
|
|
1160
1231
|
});
|
|
1161
1232
|
// Parse AI response as JSON using shared function
|
|
1162
1233
|
let visualizationResponse;
|
|
1163
1234
|
let isFallbackResponse = false;
|
|
1164
1235
|
try {
|
|
1165
|
-
const toolsUsed = [
|
|
1236
|
+
const toolsUsed = [
|
|
1237
|
+
...new Set(result.toolCallsExecuted.map(tc => tc.tool)),
|
|
1238
|
+
];
|
|
1166
1239
|
visualizationResponse = (0, visualization_1.parseVisualizationResponse)(result.finalMessage, toolsUsed);
|
|
1167
1240
|
}
|
|
1168
1241
|
catch (parseError) {
|
|
1169
|
-
this.logger.error('Failed to parse AI visualization response', parseError instanceof Error
|
|
1242
|
+
this.logger.error('Failed to parse AI visualization response', parseError instanceof Error
|
|
1243
|
+
? parseError
|
|
1244
|
+
: new Error(String(parseError)), {
|
|
1170
1245
|
requestId,
|
|
1171
1246
|
sessionIds,
|
|
1172
|
-
rawResponse: result.finalMessage.substring(0, 500)
|
|
1247
|
+
rawResponse: result.finalMessage.substring(0, 500),
|
|
1173
1248
|
});
|
|
1174
1249
|
// Fallback to basic visualization on parse error
|
|
1175
1250
|
// NOTE: isFallbackResponse flag prevents caching this response
|
|
@@ -1183,13 +1258,13 @@ class RestApiRouter {
|
|
|
1183
1258
|
type: 'code',
|
|
1184
1259
|
content: {
|
|
1185
1260
|
language: 'json',
|
|
1186
|
-
code: JSON.stringify(isMultiSession
|
|
1187
|
-
|
|
1188
|
-
|
|
1261
|
+
code: JSON.stringify(isMultiSession
|
|
1262
|
+
? sessions.map(s => s.data)
|
|
1263
|
+
: primarySession.data, null, 2),
|
|
1264
|
+
},
|
|
1265
|
+
},
|
|
1189
1266
|
],
|
|
1190
|
-
insights: [
|
|
1191
|
-
'AI visualization generation failed - showing raw data'
|
|
1192
|
-
]
|
|
1267
|
+
insights: ['AI visualization generation failed - showing raw data'],
|
|
1193
1268
|
};
|
|
1194
1269
|
}
|
|
1195
1270
|
// Cache the visualization in the session for subsequent requests (single session only)
|
|
@@ -1203,13 +1278,19 @@ class RestApiRouter {
|
|
|
1203
1278
|
visualizations: visualizationResponse.visualizations,
|
|
1204
1279
|
insights: visualizationResponse.insights,
|
|
1205
1280
|
toolsUsed: visualizationResponse.toolsUsed, // PRD #320: Cache toolsUsed
|
|
1206
|
-
generatedAt: new Date().toISOString()
|
|
1207
|
-
}
|
|
1281
|
+
generatedAt: new Date().toISOString(),
|
|
1282
|
+
},
|
|
1283
|
+
});
|
|
1284
|
+
this.logger.info('Visualization cached in session', {
|
|
1285
|
+
requestId,
|
|
1286
|
+
sessionId: sessionIds[0],
|
|
1208
1287
|
});
|
|
1209
|
-
this.logger.info('Visualization cached in session', { requestId, sessionId: sessionIds[0] });
|
|
1210
1288
|
}
|
|
1211
1289
|
else if (isFallbackResponse) {
|
|
1212
|
-
this.logger.warn('Skipping cache for fallback visualization response', {
|
|
1290
|
+
this.logger.warn('Skipping cache for fallback visualization response', {
|
|
1291
|
+
requestId,
|
|
1292
|
+
sessionId: sessionIds[0],
|
|
1293
|
+
});
|
|
1213
1294
|
}
|
|
1214
1295
|
const response = {
|
|
1215
1296
|
success: true,
|
|
@@ -1217,8 +1298,8 @@ class RestApiRouter {
|
|
|
1217
1298
|
meta: {
|
|
1218
1299
|
timestamp: new Date().toISOString(),
|
|
1219
1300
|
requestId,
|
|
1220
|
-
version: this.config.version
|
|
1221
|
-
}
|
|
1301
|
+
version: this.config.version,
|
|
1302
|
+
},
|
|
1222
1303
|
};
|
|
1223
1304
|
await this.sendJsonResponse(res, HttpStatus.OK, response);
|
|
1224
1305
|
this.logger.info('Visualization request completed', {
|
|
@@ -1226,14 +1307,14 @@ class RestApiRouter {
|
|
|
1226
1307
|
sessionIds,
|
|
1227
1308
|
visualizationCount: visualizationResponse.visualizations.length,
|
|
1228
1309
|
cached: !isMultiSession && !isFallbackResponse,
|
|
1229
|
-
isFallback: isFallbackResponse
|
|
1310
|
+
isFallback: isFallbackResponse,
|
|
1230
1311
|
});
|
|
1231
1312
|
}
|
|
1232
1313
|
catch (error) {
|
|
1233
1314
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1234
1315
|
this.logger.error('Visualization request failed', error instanceof Error ? error : new Error(String(error)), {
|
|
1235
1316
|
requestId,
|
|
1236
|
-
sessionIdParam
|
|
1317
|
+
sessionIdParam,
|
|
1237
1318
|
});
|
|
1238
1319
|
await this.sendErrorResponse(res, requestId, HttpStatus.INTERNAL_SERVER_ERROR, 'VISUALIZATION_ERROR', 'Failed to generate visualization', { error: errorMessage });
|
|
1239
1320
|
}
|
|
@@ -1249,7 +1330,7 @@ class RestApiRouter {
|
|
|
1249
1330
|
this.logger.info('Processing session retrieval', {
|
|
1250
1331
|
requestId,
|
|
1251
1332
|
sessionId,
|
|
1252
|
-
sessionPrefix
|
|
1333
|
+
sessionPrefix,
|
|
1253
1334
|
});
|
|
1254
1335
|
const sessionManager = new generic_session_manager_1.GenericSessionManager(sessionPrefix);
|
|
1255
1336
|
const session = sessionManager.getSession(sessionId);
|
|
@@ -1263,21 +1344,21 @@ class RestApiRouter {
|
|
|
1263
1344
|
meta: {
|
|
1264
1345
|
timestamp: new Date().toISOString(),
|
|
1265
1346
|
requestId,
|
|
1266
|
-
version: this.config.version
|
|
1267
|
-
}
|
|
1347
|
+
version: this.config.version,
|
|
1348
|
+
},
|
|
1268
1349
|
};
|
|
1269
1350
|
await this.sendJsonResponse(res, HttpStatus.OK, response);
|
|
1270
1351
|
this.logger.info('Session retrieved successfully', {
|
|
1271
1352
|
requestId,
|
|
1272
1353
|
sessionId,
|
|
1273
|
-
toolName: session.data?.toolName || 'unknown'
|
|
1354
|
+
toolName: session.data?.toolName || 'unknown',
|
|
1274
1355
|
});
|
|
1275
1356
|
}
|
|
1276
1357
|
catch (error) {
|
|
1277
1358
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1278
1359
|
this.logger.error('Session retrieval failed', error instanceof Error ? error : new Error(String(error)), {
|
|
1279
1360
|
requestId,
|
|
1280
|
-
sessionId
|
|
1361
|
+
sessionId,
|
|
1281
1362
|
});
|
|
1282
1363
|
await this.sendErrorResponse(res, requestId, HttpStatus.INTERNAL_SERVER_ERROR, 'SESSION_RETRIEVAL_ERROR', 'Failed to retrieve session', { error: errorMessage });
|
|
1283
1364
|
}
|
|
@@ -1306,7 +1387,12 @@ class RestApiRouter {
|
|
|
1306
1387
|
const queryResponse = await (0, plugin_registry_1.invokePluginTool)(PLUGIN_NAME, 'vector_query', {
|
|
1307
1388
|
collection: KNOWLEDGE_COLLECTION,
|
|
1308
1389
|
filter: {
|
|
1309
|
-
must: [
|
|
1390
|
+
must: [
|
|
1391
|
+
{
|
|
1392
|
+
key: 'metadata.sourceIdentifier',
|
|
1393
|
+
match: { value: decodedSourceIdentifier },
|
|
1394
|
+
},
|
|
1395
|
+
],
|
|
1310
1396
|
},
|
|
1311
1397
|
limit: 10000, // High limit to get all chunks for a source
|
|
1312
1398
|
});
|
|
@@ -1314,7 +1400,8 @@ class RestApiRouter {
|
|
|
1314
1400
|
const error = queryResponse.error;
|
|
1315
1401
|
const errorMessage = error?.message || error?.error || 'Query failed';
|
|
1316
1402
|
// If collection doesn't exist (Not Found), return success with 0 deleted
|
|
1317
|
-
if (errorMessage.includes('Not Found') ||
|
|
1403
|
+
if (errorMessage.includes('Not Found') ||
|
|
1404
|
+
errorMessage.includes('not found')) {
|
|
1318
1405
|
this.logger.info('Collection not found - returning success with 0 deleted', {
|
|
1319
1406
|
requestId,
|
|
1320
1407
|
sourceIdentifier: decodedSourceIdentifier,
|
|
@@ -1346,7 +1433,8 @@ class RestApiRouter {
|
|
|
1346
1433
|
if (!queryResult.success) {
|
|
1347
1434
|
const errorMessage = queryResult.error || queryResult.message;
|
|
1348
1435
|
// If collection doesn't exist, return success with 0 deleted
|
|
1349
|
-
if (errorMessage.includes('Not Found') ||
|
|
1436
|
+
if (errorMessage.includes('Not Found') ||
|
|
1437
|
+
errorMessage.includes('not found')) {
|
|
1350
1438
|
this.logger.info('Collection not found - returning success with 0 deleted', {
|
|
1351
1439
|
requestId,
|
|
1352
1440
|
sourceIdentifier: decodedSourceIdentifier,
|
|
@@ -1407,7 +1495,11 @@ class RestApiRouter {
|
|
|
1407
1495
|
chunkId: chunk.id,
|
|
1408
1496
|
deletedSoFar: deletedCount,
|
|
1409
1497
|
});
|
|
1410
|
-
await this.sendErrorResponse(res, requestId, HttpStatus.INTERNAL_SERVER_ERROR, 'DELETE_SOURCE_ERROR', 'Failed to delete chunk', {
|
|
1498
|
+
await this.sendErrorResponse(res, requestId, HttpStatus.INTERNAL_SERVER_ERROR, 'DELETE_SOURCE_ERROR', 'Failed to delete chunk', {
|
|
1499
|
+
chunkId: chunk.id,
|
|
1500
|
+
error: errorMessage,
|
|
1501
|
+
chunksDeletedBeforeFailure: deletedCount,
|
|
1502
|
+
});
|
|
1411
1503
|
return;
|
|
1412
1504
|
}
|
|
1413
1505
|
deletedCount++;
|
|
@@ -1452,7 +1544,7 @@ class RestApiRouter {
|
|
|
1452
1544
|
await this.sendErrorResponse(res, requestId, HttpStatus.BAD_REQUEST, 'BAD_REQUEST', 'Request body must be a JSON object');
|
|
1453
1545
|
return;
|
|
1454
1546
|
}
|
|
1455
|
-
const { query, limit = 20, uriFilter } = body;
|
|
1547
|
+
const { query, limit = 20, uriFilter, } = body;
|
|
1456
1548
|
// Validate limit parameter (must be positive integer)
|
|
1457
1549
|
if (typeof limit !== 'number' || !Number.isInteger(limit) || limit < 1) {
|
|
1458
1550
|
await this.sendErrorResponse(res, requestId, HttpStatus.BAD_REQUEST, 'INVALID_PARAMETER', 'The "limit" parameter must be a positive integer');
|
|
@@ -1607,6 +1699,41 @@ class RestApiRouter {
|
|
|
1607
1699
|
await this.sendErrorResponse(res, requestId, HttpStatus.INTERNAL_SERVER_ERROR, 'SYNTHESIS_ERROR', 'Failed to process knowledge ask request', { error: errorMessage });
|
|
1608
1700
|
}
|
|
1609
1701
|
}
|
|
1702
|
+
/**
|
|
1703
|
+
* Handle embedding migration request (PRD #384)
|
|
1704
|
+
*/
|
|
1705
|
+
async handleEmbeddingMigrationRequest(req, res, requestId, body) {
|
|
1706
|
+
try {
|
|
1707
|
+
this.logger.info('Processing embedding migration request', { requestId });
|
|
1708
|
+
// Delegate to the embedding migration handler
|
|
1709
|
+
const response = await (0, embedding_migration_handler_1.handleEmbeddingMigration)(body, this.logger, requestId);
|
|
1710
|
+
// Determine HTTP status based on response
|
|
1711
|
+
let httpStatus = HttpStatus.OK;
|
|
1712
|
+
if (!response.success) {
|
|
1713
|
+
const errorCode = response.error?.code;
|
|
1714
|
+
if (errorCode === 'PLUGIN_UNAVAILABLE' ||
|
|
1715
|
+
errorCode === 'EMBEDDING_SERVICE_UNAVAILABLE') {
|
|
1716
|
+
httpStatus = HttpStatus.SERVICE_UNAVAILABLE;
|
|
1717
|
+
}
|
|
1718
|
+
else if (errorCode === 'MIGRATION_ERROR') {
|
|
1719
|
+
httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
|
|
1720
|
+
}
|
|
1721
|
+
else {
|
|
1722
|
+
httpStatus = HttpStatus.BAD_REQUEST;
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
await this.sendJsonResponse(res, httpStatus, response);
|
|
1726
|
+
this.logger.info('Embedding migration request completed', {
|
|
1727
|
+
requestId,
|
|
1728
|
+
success: response.success,
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1731
|
+
catch (error) {
|
|
1732
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1733
|
+
this.logger.error('Embedding migration request failed', error instanceof Error ? error : new Error(String(error)), { requestId });
|
|
1734
|
+
await this.sendErrorResponse(res, requestId, HttpStatus.INTERNAL_SERVER_ERROR, 'MIGRATION_ERROR', 'Failed to process embedding migration request', { error: errorMessage });
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1610
1737
|
/**
|
|
1611
1738
|
* Set CORS headers
|
|
1612
1739
|
*/
|
|
@@ -1632,13 +1759,13 @@ class RestApiRouter {
|
|
|
1632
1759
|
error: {
|
|
1633
1760
|
code,
|
|
1634
1761
|
message,
|
|
1635
|
-
details
|
|
1762
|
+
details,
|
|
1636
1763
|
},
|
|
1637
1764
|
meta: {
|
|
1638
1765
|
timestamp: new Date().toISOString(),
|
|
1639
1766
|
requestId,
|
|
1640
|
-
version: this.config.version
|
|
1641
|
-
}
|
|
1767
|
+
version: this.config.version,
|
|
1768
|
+
},
|
|
1642
1769
|
};
|
|
1643
1770
|
await this.sendJsonResponse(res, status, response);
|
|
1644
1771
|
}
|