@vfarcic/dot-ai 1.0.3 → 1.1.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/README.md +1 -0
- package/dist/core/ai-provider.interface.d.ts +12 -8
- package/dist/core/ai-provider.interface.d.ts.map +1 -1
- package/dist/core/artifacthub.d.ts +1 -1
- package/dist/core/artifacthub.d.ts.map +1 -1
- package/dist/core/base-vector-service.d.ts +22 -9
- package/dist/core/base-vector-service.d.ts.map +1 -1
- package/dist/core/base-vector-service.js +106 -37
- package/dist/core/capabilities.d.ts.map +1 -1
- package/dist/core/capabilities.js +5 -2
- package/dist/core/capability-operations.d.ts +55 -7
- package/dist/core/capability-operations.d.ts.map +1 -1
- package/dist/core/capability-operations.js +1 -3
- package/dist/core/capability-scan-workflow.d.ts +64 -8
- package/dist/core/capability-scan-workflow.d.ts.map +1 -1
- package/dist/core/capability-scan-workflow.js +14 -13
- package/dist/core/capability-tools.d.ts +1 -1
- package/dist/core/capability-tools.d.ts.map +1 -1
- package/dist/core/capability-tools.js +1 -1
- package/dist/core/capability-vector-service.d.ts +3 -4
- package/dist/core/capability-vector-service.d.ts.map +1 -1
- package/dist/core/capability-vector-service.js +2 -2
- package/dist/core/command-executor.d.ts +3 -4
- package/dist/core/command-executor.d.ts.map +1 -1
- package/dist/core/command-executor.js +8 -4
- package/dist/core/crd-availability.d.ts +3 -5
- package/dist/core/crd-availability.d.ts.map +1 -1
- package/dist/core/crd-availability.js +8 -18
- package/dist/core/deploy-operation.d.ts +6 -5
- package/dist/core/deploy-operation.d.ts.map +1 -1
- package/dist/core/deploy-operation.js +16 -10
- package/dist/core/discovery.d.ts +6 -14
- package/dist/core/discovery.d.ts.map +1 -1
- package/dist/core/discovery.js +35 -51
- package/dist/core/embedding-service.d.ts.map +1 -1
- package/dist/core/embedding-service.js +1 -1
- package/dist/core/error-handling.d.ts +13 -13
- package/dist/core/error-handling.d.ts.map +1 -1
- package/dist/core/error-handling.js +2 -3
- package/dist/core/generic-session-manager.d.ts +2 -2
- package/dist/core/generic-session-manager.d.ts.map +1 -1
- package/dist/core/helm-types.d.ts +5 -5
- package/dist/core/helm-types.d.ts.map +1 -1
- package/dist/core/index.d.ts +4 -11
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +8 -14
- package/dist/core/knowledge-types.d.ts +114 -0
- package/dist/core/knowledge-types.d.ts.map +1 -0
- package/dist/core/knowledge-types.js +10 -0
- package/dist/core/memory.d.ts +12 -12
- package/dist/core/memory.d.ts.map +1 -1
- package/dist/core/mermaid-tools.d.ts +24 -1
- package/dist/core/mermaid-tools.d.ts.map +1 -1
- package/dist/core/mermaid-tools.js +10 -8
- package/dist/core/packaging.d.ts +23 -1
- package/dist/core/packaging.d.ts.map +1 -1
- package/dist/core/pattern-operations.d.ts +32 -9
- package/dist/core/pattern-operations.d.ts.map +1 -1
- package/dist/core/pattern-operations.js +17 -22
- package/dist/core/pattern-vector-service.d.ts +3 -4
- package/dist/core/pattern-vector-service.d.ts.map +1 -1
- package/dist/core/pattern-vector-service.js +2 -2
- package/dist/core/platform-utils.d.ts +2 -2
- package/dist/core/platform-utils.d.ts.map +1 -1
- package/dist/core/plugin-manager.d.ts +6 -2
- package/dist/core/plugin-manager.d.ts.map +1 -1
- package/dist/core/plugin-manager.js +9 -16
- package/dist/core/plugin-registry.d.ts +59 -0
- package/dist/core/plugin-registry.d.ts.map +1 -0
- package/dist/core/plugin-registry.js +80 -0
- package/dist/core/policy-operations.d.ts +101 -21
- package/dist/core/policy-operations.d.ts.map +1 -1
- package/dist/core/policy-operations.js +45 -47
- package/dist/core/policy-vector-service.d.ts +3 -4
- package/dist/core/policy-vector-service.d.ts.map +1 -1
- package/dist/core/policy-vector-service.js +2 -2
- package/dist/core/providers/host-provider.d.ts +1 -1
- package/dist/core/providers/host-provider.d.ts.map +1 -1
- package/dist/core/providers/host-provider.js +2 -2
- package/dist/core/providers/provider-debug-utils.d.ts +2 -2
- package/dist/core/providers/provider-debug-utils.d.ts.map +1 -1
- package/dist/core/providers/tool-utils.d.ts +10 -2
- package/dist/core/providers/tool-utils.d.ts.map +1 -1
- package/dist/core/providers/tool-utils.js +2 -2
- package/dist/core/providers/vercel-provider.d.ts.map +1 -1
- package/dist/core/providers/vercel-provider.js +29 -23
- package/dist/core/resource-tools.d.ts +29 -1
- package/dist/core/resource-tools.d.ts.map +1 -1
- package/dist/core/resource-vector-service.d.ts +3 -4
- package/dist/core/resource-vector-service.d.ts.map +1 -1
- package/dist/core/resource-vector-service.js +2 -2
- package/dist/core/schema.d.ts +15 -14
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +32 -34
- package/dist/core/shared-prompt-loader.d.ts +1 -1
- package/dist/core/shared-prompt-loader.d.ts.map +1 -1
- package/dist/core/solution-cr.js +1 -1
- package/dist/core/solution-utils.d.ts +22 -3
- package/dist/core/solution-utils.d.ts.map +1 -1
- package/dist/core/solution-utils.js +1 -1
- package/dist/core/telemetry/client.d.ts +0 -6
- package/dist/core/telemetry/client.d.ts.map +1 -1
- package/dist/core/telemetry/client.js +6 -17
- package/dist/core/telemetry/config.js +1 -1
- package/dist/core/telemetry/index.d.ts +1 -1
- package/dist/core/telemetry/index.d.ts.map +1 -1
- package/dist/core/telemetry/index.js +1 -2
- package/dist/core/tracing/tool-tracing.d.ts +1 -1
- package/dist/core/tracing/tool-tracing.d.ts.map +1 -1
- package/dist/core/unified-creation-session.d.ts +15 -8
- package/dist/core/unified-creation-session.d.ts.map +1 -1
- package/dist/core/unified-creation-session.js +19 -19
- package/dist/core/unified-creation-types.d.ts +2 -2
- package/dist/core/unified-creation-types.d.ts.map +1 -1
- package/dist/core/visualization.d.ts +1 -1
- package/dist/core/visualization.d.ts.map +1 -1
- package/dist/core/workflow.d.ts +8 -5
- package/dist/core/workflow.d.ts.map +1 -1
- package/dist/evaluation/dataset-analyzer.d.ts +13 -7
- package/dist/evaluation/dataset-analyzer.d.ts.map +1 -1
- package/dist/evaluation/dataset-analyzer.js +1 -1
- package/dist/evaluation/datasets/loader.d.ts +2 -2
- package/dist/evaluation/datasets/loader.d.ts.map +1 -1
- package/dist/evaluation/eval-runner.js +7 -5
- package/dist/evaluation/evaluators/base-comparative.d.ts +1 -1
- package/dist/evaluation/evaluators/base-comparative.d.ts.map +1 -1
- package/dist/evaluation/evaluators/base-comparative.js +4 -3
- package/dist/evaluation/evaluators/base.d.ts +5 -5
- package/dist/evaluation/evaluators/base.d.ts.map +1 -1
- package/dist/evaluation/evaluators/capability-comparative.js +1 -1
- package/dist/evaluation/platform-synthesizer.d.ts.map +1 -1
- package/dist/interfaces/mcp.d.ts.map +1 -1
- package/dist/interfaces/mcp.js +26 -15
- package/dist/interfaces/openapi-generator.d.ts +116 -12
- package/dist/interfaces/openapi-generator.d.ts.map +1 -1
- package/dist/interfaces/openapi-generator.js +490 -199
- package/dist/interfaces/rest-api.d.ts +28 -6
- package/dist/interfaces/rest-api.d.ts.map +1 -1
- package/dist/interfaces/rest-api.js +436 -245
- package/dist/interfaces/rest-registry.d.ts +4 -4
- package/dist/interfaces/rest-registry.d.ts.map +1 -1
- package/dist/interfaces/rest-registry.js +6 -5
- package/dist/interfaces/rest-route-registry.d.ts +165 -0
- package/dist/interfaces/rest-route-registry.d.ts.map +1 -0
- package/dist/interfaces/rest-route-registry.js +230 -0
- package/dist/interfaces/routes/index.d.ts +22 -0
- package/dist/interfaces/routes/index.d.ts.map +1 -0
- package/dist/interfaces/routes/index.js +347 -0
- package/dist/interfaces/schemas/common.d.ts +177 -0
- package/dist/interfaces/schemas/common.d.ts.map +1 -0
- package/dist/interfaces/schemas/common.js +102 -0
- package/dist/interfaces/schemas/events.d.ts +131 -0
- package/dist/interfaces/schemas/events.d.ts.map +1 -0
- package/dist/interfaces/schemas/events.js +66 -0
- package/dist/interfaces/schemas/index.d.ts +21 -0
- package/dist/interfaces/schemas/index.d.ts.map +1 -0
- package/dist/interfaces/schemas/index.js +138 -0
- package/dist/interfaces/schemas/knowledge.d.ts +210 -0
- package/dist/interfaces/schemas/knowledge.d.ts.map +1 -0
- package/dist/interfaces/schemas/knowledge.js +117 -0
- package/dist/interfaces/schemas/logs.d.ts +82 -0
- package/dist/interfaces/schemas/logs.d.ts.map +1 -0
- package/dist/interfaces/schemas/logs.js +46 -0
- package/dist/interfaces/schemas/prompts.d.ts +191 -0
- package/dist/interfaces/schemas/prompts.d.ts.map +1 -0
- package/dist/interfaces/schemas/prompts.js +91 -0
- package/dist/interfaces/schemas/resources.d.ts +378 -0
- package/dist/interfaces/schemas/resources.d.ts.map +1 -0
- package/dist/interfaces/schemas/resources.js +173 -0
- package/dist/interfaces/schemas/sessions.d.ts +90 -0
- package/dist/interfaces/schemas/sessions.d.ts.map +1 -0
- package/dist/interfaces/schemas/sessions.js +56 -0
- package/dist/interfaces/schemas/tools.d.ts +194 -0
- package/dist/interfaces/schemas/tools.d.ts.map +1 -0
- package/dist/interfaces/schemas/tools.js +101 -0
- package/dist/interfaces/schemas/visualization.d.ts +373 -0
- package/dist/interfaces/schemas/visualization.d.ts.map +1 -0
- package/dist/interfaces/schemas/visualization.js +134 -0
- package/dist/mcp/server.js +5 -4
- package/dist/tools/answer-question.d.ts +1 -1
- package/dist/tools/answer-question.d.ts.map +1 -1
- package/dist/tools/answer-question.js +9 -8
- package/dist/tools/deploy-manifests.d.ts +4 -2
- package/dist/tools/deploy-manifests.d.ts.map +1 -1
- package/dist/tools/deploy-manifests.js +10 -6
- package/dist/tools/generate-manifests.d.ts.map +1 -1
- package/dist/tools/generate-manifests.js +28 -20
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +6 -1
- package/dist/tools/manage-knowledge.d.ts +77 -0
- package/dist/tools/manage-knowledge.d.ts.map +1 -0
- package/dist/tools/manage-knowledge.js +573 -0
- package/dist/tools/operate-analysis.d.ts +31 -2
- package/dist/tools/operate-analysis.d.ts.map +1 -1
- package/dist/tools/operate-execution.d.ts +2 -3
- package/dist/tools/operate-execution.d.ts.map +1 -1
- package/dist/tools/operate-execution.js +7 -7
- package/dist/tools/operate.d.ts +7 -2
- package/dist/tools/operate.d.ts.map +1 -1
- package/dist/tools/operate.js +2 -2
- package/dist/tools/organizational-data.d.ts +30 -4
- package/dist/tools/organizational-data.d.ts.map +1 -1
- package/dist/tools/organizational-data.js +24 -19
- package/dist/tools/project-setup/discovery.d.ts.map +1 -1
- package/dist/tools/project-setup/generate-scope.d.ts +1 -1
- package/dist/tools/project-setup/generate-scope.d.ts.map +1 -1
- package/dist/tools/project-setup/types.d.ts +1 -0
- package/dist/tools/project-setup/types.d.ts.map +1 -1
- package/dist/tools/prompts.d.ts +28 -2
- package/dist/tools/prompts.d.ts.map +1 -1
- package/dist/tools/query.d.ts +17 -3
- package/dist/tools/query.d.ts.map +1 -1
- package/dist/tools/query.js +1 -7
- package/dist/tools/recommend.d.ts +24 -6
- package/dist/tools/recommend.d.ts.map +1 -1
- package/dist/tools/recommend.js +18 -15
- package/dist/tools/remediate.d.ts +12 -3
- package/dist/tools/remediate.d.ts.map +1 -1
- package/dist/tools/remediate.js +22 -14
- package/dist/tools/version.d.ts +19 -5
- package/dist/tools/version.d.ts.map +1 -1
- package/dist/tools/version.js +106 -54
- package/package.json +11 -5
- package/prompts/knowledge-ask.md +29 -0
- package/dist/core/vector-db-service.d.ts +0 -108
- package/dist/core/vector-db-service.d.ts.map +0 -1
- package/dist/core/vector-db-service.js +0 -647
|
@@ -42,6 +42,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
42
42
|
exports.RestApiRouter = exports.HttpStatus = void 0;
|
|
43
43
|
const node_url_1 = require("node:url");
|
|
44
44
|
const openapi_generator_1 = require("./openapi-generator");
|
|
45
|
+
const rest_route_registry_1 = require("./rest-route-registry");
|
|
46
|
+
const routes_1 = require("./routes");
|
|
45
47
|
const resource_sync_handler_1 = require("./resource-sync-handler");
|
|
46
48
|
const prompts_1 = require("../tools/prompts");
|
|
47
49
|
const generic_session_manager_1 = require("../core/generic-session-manager");
|
|
@@ -51,6 +53,8 @@ const ai_provider_factory_1 = require("../core/ai-provider-factory");
|
|
|
51
53
|
const capability_tools_1 = require("../core/capability-tools");
|
|
52
54
|
const resource_tools_1 = require("../core/resource-tools");
|
|
53
55
|
const mermaid_tools_1 = require("../core/mermaid-tools");
|
|
56
|
+
const plugin_registry_1 = require("../core/plugin-registry");
|
|
57
|
+
const manage_knowledge_1 = require("../tools/manage-knowledge");
|
|
54
58
|
/**
|
|
55
59
|
* HTTP status codes for REST responses
|
|
56
60
|
*/
|
|
@@ -69,6 +73,7 @@ var HttpStatus;
|
|
|
69
73
|
*/
|
|
70
74
|
class RestApiRouter {
|
|
71
75
|
registry;
|
|
76
|
+
routeRegistry;
|
|
72
77
|
logger;
|
|
73
78
|
dotAI;
|
|
74
79
|
config;
|
|
@@ -87,14 +92,23 @@ class RestApiRouter {
|
|
|
87
92
|
requestTimeout: 1800000, // 30 minutes for long-running operations (capability scan with slower AI providers)
|
|
88
93
|
...config
|
|
89
94
|
};
|
|
90
|
-
// Initialize
|
|
95
|
+
// Initialize route registry and register all routes (PRD #354)
|
|
96
|
+
this.routeRegistry = new rest_route_registry_1.RestRouteRegistry(logger);
|
|
97
|
+
(0, routes_1.registerAllRoutes)(this.routeRegistry);
|
|
98
|
+
this.logger.info('REST route registry initialized', {
|
|
99
|
+
routeCount: this.routeRegistry.getRouteCount(),
|
|
100
|
+
tags: this.routeRegistry.getTags(),
|
|
101
|
+
});
|
|
102
|
+
// Initialize OpenAPI generator with route registry (PRD #354)
|
|
91
103
|
this.openApiGenerator = new openapi_generator_1.OpenApiGenerator(registry, logger, {
|
|
92
104
|
basePath: this.config.basePath,
|
|
93
|
-
apiVersion: this.config.version
|
|
94
|
-
});
|
|
105
|
+
apiVersion: this.config.version,
|
|
106
|
+
}, this.routeRegistry);
|
|
95
107
|
}
|
|
96
108
|
/**
|
|
97
109
|
* Handle incoming HTTP requests for REST API
|
|
110
|
+
*
|
|
111
|
+
* PRD #354: Uses route registry for matching, dispatches to handlers based on route path.
|
|
98
112
|
*/
|
|
99
113
|
async handleRequest(req, res, body) {
|
|
100
114
|
const requestId = this.generateRequestId();
|
|
@@ -115,141 +129,35 @@ class RestApiRouter {
|
|
|
115
129
|
return;
|
|
116
130
|
}
|
|
117
131
|
}
|
|
118
|
-
// Parse URL
|
|
132
|
+
// Parse URL
|
|
119
133
|
const url = new node_url_1.URL(req.url || '/', 'http://localhost');
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
134
|
+
const method = req.method || 'GET';
|
|
135
|
+
// PRD #354: Try route registry first
|
|
136
|
+
const routeMatch = this.routeRegistry.findRoute(method, url.pathname);
|
|
137
|
+
if (routeMatch) {
|
|
138
|
+
this.logger.debug('Route matched via registry', {
|
|
139
|
+
requestId,
|
|
140
|
+
path: routeMatch.route.path,
|
|
141
|
+
method: routeMatch.route.method,
|
|
142
|
+
params: routeMatch.params,
|
|
143
|
+
});
|
|
144
|
+
// Dispatch to handler based on route path
|
|
145
|
+
await this.dispatchRoute(req, res, requestId, routeMatch, url.searchParams, body, startTime);
|
|
123
146
|
return;
|
|
124
147
|
}
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
case 'tool':
|
|
136
|
-
if (req.method === 'POST' && pathMatch.toolName) {
|
|
137
|
-
await this.handleToolExecution(req, res, requestId, pathMatch.toolName, body, startTime);
|
|
138
|
-
}
|
|
139
|
-
else if (req.method !== 'POST') {
|
|
140
|
-
await this.sendErrorResponse(res, requestId, HttpStatus.METHOD_NOT_ALLOWED, 'METHOD_NOT_ALLOWED', 'Only POST method allowed for tool execution');
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
await this.sendErrorResponse(res, requestId, HttpStatus.BAD_REQUEST, 'BAD_REQUEST', 'Tool name is required');
|
|
144
|
-
}
|
|
145
|
-
break;
|
|
146
|
-
case 'openapi':
|
|
147
|
-
if (req.method === 'GET') {
|
|
148
|
-
await this.handleOpenApiSpec(req, res, requestId);
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
await this.sendErrorResponse(res, requestId, HttpStatus.METHOD_NOT_ALLOWED, 'METHOD_NOT_ALLOWED', 'Only GET method allowed for OpenAPI specification');
|
|
152
|
-
}
|
|
153
|
-
break;
|
|
154
|
-
case 'resources':
|
|
155
|
-
if (req.method === 'POST' && pathMatch.action === 'sync') {
|
|
156
|
-
await this.handleResourceSyncRequest(req, res, requestId, body);
|
|
157
|
-
}
|
|
158
|
-
else if (req.method === 'GET' && pathMatch.action === 'kinds') {
|
|
159
|
-
await this.handleGetResourceKinds(req, res, requestId, url.searchParams);
|
|
160
|
-
}
|
|
161
|
-
else if (req.method === 'GET' && pathMatch.action === 'search') {
|
|
162
|
-
await this.handleSearchResources(req, res, requestId, url.searchParams);
|
|
163
|
-
}
|
|
164
|
-
else if (req.method === 'GET' && pathMatch.action === 'list') {
|
|
165
|
-
await this.handleListResources(req, res, requestId, url.searchParams);
|
|
166
|
-
}
|
|
167
|
-
else if (pathMatch.action === 'sync' && req.method !== 'POST') {
|
|
168
|
-
await this.sendErrorResponse(res, requestId, HttpStatus.METHOD_NOT_ALLOWED, 'METHOD_NOT_ALLOWED', 'Only POST method allowed for resource sync');
|
|
169
|
-
}
|
|
170
|
-
else if ((pathMatch.action === 'kinds' || pathMatch.action === 'list' || pathMatch.action === 'search') && req.method !== 'GET') {
|
|
171
|
-
await this.sendErrorResponse(res, requestId, HttpStatus.METHOD_NOT_ALLOWED, 'METHOD_NOT_ALLOWED', 'Only GET method allowed for this endpoint');
|
|
172
|
-
}
|
|
173
|
-
else {
|
|
174
|
-
await this.sendErrorResponse(res, requestId, HttpStatus.NOT_FOUND, 'NOT_FOUND', 'Unknown resources endpoint');
|
|
175
|
-
}
|
|
176
|
-
break;
|
|
177
|
-
case 'namespaces':
|
|
178
|
-
if (req.method === 'GET') {
|
|
179
|
-
await this.handleGetNamespaces(req, res, requestId);
|
|
180
|
-
}
|
|
181
|
-
else {
|
|
182
|
-
await this.sendErrorResponse(res, requestId, HttpStatus.METHOD_NOT_ALLOWED, 'METHOD_NOT_ALLOWED', 'Only GET method allowed for namespaces');
|
|
183
|
-
}
|
|
184
|
-
break;
|
|
185
|
-
case 'resource':
|
|
186
|
-
if (req.method === 'GET') {
|
|
187
|
-
await this.handleGetResource(req, res, requestId, url.searchParams);
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
await this.sendErrorResponse(res, requestId, HttpStatus.METHOD_NOT_ALLOWED, 'METHOD_NOT_ALLOWED', 'Only GET method allowed for resource');
|
|
191
|
-
}
|
|
192
|
-
break;
|
|
193
|
-
case 'events':
|
|
194
|
-
if (req.method === 'GET') {
|
|
195
|
-
await this.handleGetEvents(req, res, requestId, url.searchParams);
|
|
196
|
-
}
|
|
197
|
-
else {
|
|
198
|
-
await this.sendErrorResponse(res, requestId, HttpStatus.METHOD_NOT_ALLOWED, 'METHOD_NOT_ALLOWED', 'Only GET method allowed for events');
|
|
199
|
-
}
|
|
200
|
-
break;
|
|
201
|
-
case 'logs':
|
|
202
|
-
if (req.method === 'GET') {
|
|
203
|
-
await this.handleGetLogs(req, res, requestId, url.searchParams);
|
|
204
|
-
}
|
|
205
|
-
else {
|
|
206
|
-
await this.sendErrorResponse(res, requestId, HttpStatus.METHOD_NOT_ALLOWED, 'METHOD_NOT_ALLOWED', 'Only GET method allowed for logs');
|
|
207
|
-
}
|
|
208
|
-
break;
|
|
209
|
-
case 'prompts':
|
|
210
|
-
if (req.method === 'GET') {
|
|
211
|
-
await this.handlePromptsListRequest(req, res, requestId);
|
|
212
|
-
}
|
|
213
|
-
else {
|
|
214
|
-
await this.sendErrorResponse(res, requestId, HttpStatus.METHOD_NOT_ALLOWED, 'METHOD_NOT_ALLOWED', 'Only GET method allowed for prompts list');
|
|
215
|
-
}
|
|
216
|
-
break;
|
|
217
|
-
case 'prompt':
|
|
218
|
-
if (req.method === 'POST' && pathMatch.promptName) {
|
|
219
|
-
await this.handlePromptsGetRequest(req, res, requestId, pathMatch.promptName, body);
|
|
220
|
-
}
|
|
221
|
-
else if (req.method !== 'POST') {
|
|
222
|
-
await this.sendErrorResponse(res, requestId, HttpStatus.METHOD_NOT_ALLOWED, 'METHOD_NOT_ALLOWED', 'Only POST method allowed for prompt get');
|
|
223
|
-
}
|
|
224
|
-
else {
|
|
225
|
-
await this.sendErrorResponse(res, requestId, HttpStatus.BAD_REQUEST, 'BAD_REQUEST', 'Prompt name is required');
|
|
226
|
-
}
|
|
227
|
-
break;
|
|
228
|
-
case 'visualize':
|
|
229
|
-
if (req.method === 'GET' && pathMatch.sessionId) {
|
|
230
|
-
await this.handleVisualize(req, res, requestId, pathMatch.sessionId, url.searchParams);
|
|
231
|
-
}
|
|
232
|
-
else if (req.method !== 'GET') {
|
|
233
|
-
await this.sendErrorResponse(res, requestId, HttpStatus.METHOD_NOT_ALLOWED, 'METHOD_NOT_ALLOWED', 'Only GET method allowed for visualization');
|
|
234
|
-
}
|
|
235
|
-
else {
|
|
236
|
-
await this.sendErrorResponse(res, requestId, HttpStatus.BAD_REQUEST, 'BAD_REQUEST', 'Session ID is required');
|
|
237
|
-
}
|
|
238
|
-
break;
|
|
239
|
-
case 'sessions':
|
|
240
|
-
if (req.method === 'GET' && pathMatch.sessionId) {
|
|
241
|
-
await this.handleSessionRetrieval(req, res, requestId, pathMatch.sessionId);
|
|
242
|
-
}
|
|
243
|
-
else if (req.method !== 'GET') {
|
|
244
|
-
await this.sendErrorResponse(res, requestId, HttpStatus.METHOD_NOT_ALLOWED, 'METHOD_NOT_ALLOWED', 'Only GET method allowed for session retrieval');
|
|
245
|
-
}
|
|
246
|
-
else {
|
|
247
|
-
await this.sendErrorResponse(res, requestId, HttpStatus.BAD_REQUEST, 'BAD_REQUEST', 'Session ID is required');
|
|
248
|
-
}
|
|
249
|
-
break;
|
|
250
|
-
default:
|
|
251
|
-
await this.sendErrorResponse(res, requestId, HttpStatus.NOT_FOUND, 'NOT_FOUND', 'Unknown API endpoint');
|
|
148
|
+
// Check if path matches but method is wrong (HTTP 405 per RFC 7231)
|
|
149
|
+
const allowedMethods = this.routeRegistry.findAllowedMethods(url.pathname);
|
|
150
|
+
if (allowedMethods.length > 0) {
|
|
151
|
+
res.setHeader('Allow', allowedMethods.join(', '));
|
|
152
|
+
const methodList = allowedMethods.join(', ');
|
|
153
|
+
const message = allowedMethods.length === 1
|
|
154
|
+
? `Only ${methodList} method allowed`
|
|
155
|
+
: `Only ${methodList} methods allowed`;
|
|
156
|
+
await this.sendErrorResponse(res, requestId, HttpStatus.METHOD_NOT_ALLOWED, 'METHOD_NOT_ALLOWED', message);
|
|
157
|
+
return;
|
|
252
158
|
}
|
|
159
|
+
// No match found
|
|
160
|
+
await this.sendErrorResponse(res, requestId, HttpStatus.NOT_FOUND, 'NOT_FOUND', 'API endpoint not found');
|
|
253
161
|
}
|
|
254
162
|
catch (error) {
|
|
255
163
|
this.logger.error('REST API request failed', error instanceof Error ? error : new Error(String(error)), {
|
|
@@ -260,96 +168,40 @@ class RestApiRouter {
|
|
|
260
168
|
}
|
|
261
169
|
}
|
|
262
170
|
/**
|
|
263
|
-
*
|
|
171
|
+
* Dispatch request to appropriate handler based on matched route
|
|
172
|
+
* PRD #354: Central dispatch using handler map for registry-matched routes.
|
|
264
173
|
*/
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
//
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
return { endpoint: 'openapi' };
|
|
292
|
-
}
|
|
293
|
-
if (cleanPath.startsWith('tools/')) {
|
|
294
|
-
const toolName = cleanPath.substring(6); // Remove 'tools/'
|
|
295
|
-
if (toolName) {
|
|
296
|
-
return { endpoint: 'tool', toolName };
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
// Handle resources endpoints (PRD #328)
|
|
300
|
-
if (cleanPath === 'resources/kinds') {
|
|
301
|
-
return { endpoint: 'resources', action: 'kinds' };
|
|
302
|
-
}
|
|
303
|
-
if (cleanPath === 'resources/search') {
|
|
304
|
-
return { endpoint: 'resources', action: 'search' };
|
|
305
|
-
}
|
|
306
|
-
if (cleanPath === 'resources/sync') {
|
|
307
|
-
return { endpoint: 'resources', action: 'sync' };
|
|
308
|
-
}
|
|
309
|
-
if (cleanPath === 'resources') {
|
|
310
|
-
return { endpoint: 'resources', action: 'list' };
|
|
311
|
-
}
|
|
312
|
-
// Handle single resource endpoint (PRD #328)
|
|
313
|
-
if (cleanPath === 'resource') {
|
|
314
|
-
return { endpoint: 'resource' };
|
|
315
|
-
}
|
|
316
|
-
// Handle namespaces endpoint (PRD #328)
|
|
317
|
-
if (cleanPath === 'namespaces') {
|
|
318
|
-
return { endpoint: 'namespaces' };
|
|
319
|
-
}
|
|
320
|
-
// Handle events endpoint (PRD #328)
|
|
321
|
-
if (cleanPath === 'events') {
|
|
322
|
-
return { endpoint: 'events' };
|
|
323
|
-
}
|
|
324
|
-
// Handle logs endpoint (PRD #328)
|
|
325
|
-
if (cleanPath === 'logs') {
|
|
326
|
-
return { endpoint: 'logs' };
|
|
327
|
-
}
|
|
328
|
-
// Handle prompts endpoints
|
|
329
|
-
if (cleanPath === 'prompts') {
|
|
330
|
-
return { endpoint: 'prompts' };
|
|
331
|
-
}
|
|
332
|
-
if (cleanPath.startsWith('prompts/')) {
|
|
333
|
-
const promptName = cleanPath.substring(8); // Remove 'prompts/'
|
|
334
|
-
if (promptName) {
|
|
335
|
-
return { endpoint: 'prompt', promptName };
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
// Handle visualize endpoint (PRD #317)
|
|
339
|
-
if (cleanPath.startsWith('visualize/')) {
|
|
340
|
-
const sessionId = cleanPath.substring(10); // Remove 'visualize/'
|
|
341
|
-
if (sessionId) {
|
|
342
|
-
return { endpoint: 'visualize', sessionId };
|
|
343
|
-
}
|
|
174
|
+
async dispatchRoute(req, res, requestId, routeMatch, searchParams, body, startTime) {
|
|
175
|
+
const { route, params } = routeMatch;
|
|
176
|
+
const routeKey = `${route.method}:${route.path}`;
|
|
177
|
+
// Handler map: route key -> handler function
|
|
178
|
+
const handlers = {
|
|
179
|
+
'GET:/api/v1/tools': () => this.handleToolDiscovery(req, res, requestId, searchParams),
|
|
180
|
+
'POST:/api/v1/tools/:toolName': () => this.handleToolExecution(req, res, requestId, params.toolName, body, startTime),
|
|
181
|
+
'GET:/api/v1/openapi': () => this.handleOpenApiSpec(req, res, requestId),
|
|
182
|
+
'GET:/api/v1/resources': () => this.handleListResources(req, res, requestId, searchParams),
|
|
183
|
+
'GET:/api/v1/resources/kinds': () => this.handleGetResourceKinds(req, res, requestId, searchParams),
|
|
184
|
+
'GET:/api/v1/resources/search': () => this.handleSearchResources(req, res, requestId, searchParams),
|
|
185
|
+
'POST:/api/v1/resources/sync': () => this.handleResourceSyncRequest(req, res, requestId, body),
|
|
186
|
+
'GET:/api/v1/resource': () => this.handleGetResource(req, res, requestId, searchParams),
|
|
187
|
+
'GET:/api/v1/namespaces': () => this.handleGetNamespaces(req, res, requestId),
|
|
188
|
+
'GET:/api/v1/events': () => this.handleGetEvents(req, res, requestId, searchParams),
|
|
189
|
+
'GET:/api/v1/logs': () => this.handleGetLogs(req, res, requestId, searchParams),
|
|
190
|
+
'GET:/api/v1/prompts': () => this.handlePromptsListRequest(req, res, requestId),
|
|
191
|
+
'POST:/api/v1/prompts/:promptName': () => this.handlePromptsGetRequest(req, res, requestId, params.promptName, body),
|
|
192
|
+
'GET:/api/v1/visualize/:sessionId': () => this.handleVisualize(req, res, requestId, params.sessionId, searchParams),
|
|
193
|
+
'GET:/api/v1/sessions/:sessionId': () => this.handleSessionRetrieval(req, res, requestId, params.sessionId),
|
|
194
|
+
'DELETE:/api/v1/knowledge/source/:sourceIdentifier': () => this.handleDeleteKnowledgeSource(req, res, requestId, params.sourceIdentifier),
|
|
195
|
+
'POST:/api/v1/knowledge/ask': () => this.handleKnowledgeAsk(req, res, requestId, body),
|
|
196
|
+
};
|
|
197
|
+
const handler = handlers[routeKey];
|
|
198
|
+
if (handler) {
|
|
199
|
+
await handler();
|
|
344
200
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
if (sessionId) {
|
|
349
|
-
return { endpoint: 'sessions', sessionId };
|
|
350
|
-
}
|
|
201
|
+
else {
|
|
202
|
+
this.logger.warn('Route matched but no handler found', { requestId, routeKey });
|
|
203
|
+
await this.sendErrorResponse(res, requestId, HttpStatus.NOT_FOUND, 'NOT_FOUND', 'Handler not found for route');
|
|
351
204
|
}
|
|
352
|
-
return null;
|
|
353
205
|
}
|
|
354
206
|
/**
|
|
355
207
|
* Handle tool discovery requests
|
|
@@ -383,7 +235,7 @@ class RestApiRouter {
|
|
|
383
235
|
filters: { category, tag, search }
|
|
384
236
|
});
|
|
385
237
|
}
|
|
386
|
-
catch
|
|
238
|
+
catch {
|
|
387
239
|
await this.sendErrorResponse(res, requestId, HttpStatus.INTERNAL_SERVER_ERROR, 'DISCOVERY_ERROR', 'Failed to retrieve tool information');
|
|
388
240
|
}
|
|
389
241
|
}
|
|
@@ -721,13 +573,14 @@ class RestApiRouter {
|
|
|
721
573
|
// Fetch status via plugin separately if requested
|
|
722
574
|
const result = await (0, resource_tools_1.listResources)({ kind, apiVersion, namespace, limit, offset });
|
|
723
575
|
// Enrich with live status via plugin if requested
|
|
724
|
-
|
|
576
|
+
// PRD #359: Use unified plugin registry
|
|
577
|
+
if (includeStatus && result.resources.length > 0 && (0, plugin_registry_1.isPluginInitialized)()) {
|
|
725
578
|
const statusPromises = result.resources.map(async (resource) => {
|
|
726
579
|
const resourceType = resource.apiGroup
|
|
727
580
|
? `${resource.kind.toLowerCase()}.${resource.apiGroup}`
|
|
728
581
|
: resource.kind.toLowerCase();
|
|
729
582
|
const resourceId = `${resourceType}/${resource.name}`;
|
|
730
|
-
const pluginResponse = await
|
|
583
|
+
const pluginResponse = await (0, plugin_registry_1.invokePluginTool)('agentic-tools', 'kubectl_get_resource_json', {
|
|
731
584
|
resource: resourceId,
|
|
732
585
|
namespace: resource.namespace,
|
|
733
586
|
field: 'status'
|
|
@@ -833,15 +686,16 @@ class RestApiRouter {
|
|
|
833
686
|
this.logger.info('Processing get resource request', { requestId, kind, apiVersion, name, namespace });
|
|
834
687
|
// Extract apiGroup from apiVersion (e.g., "apps/v1" -> "apps", "v1" -> "")
|
|
835
688
|
const apiGroup = apiVersion.includes('/') ? apiVersion.split('/')[0] : '';
|
|
836
|
-
// PRD #
|
|
837
|
-
if (!
|
|
838
|
-
await this.sendErrorResponse(res, requestId, HttpStatus.SERVICE_UNAVAILABLE, 'PLUGIN_UNAVAILABLE', '
|
|
689
|
+
// PRD #359: Use unified plugin registry
|
|
690
|
+
if (!(0, plugin_registry_1.isPluginInitialized)()) {
|
|
691
|
+
await this.sendErrorResponse(res, requestId, HttpStatus.SERVICE_UNAVAILABLE, 'PLUGIN_UNAVAILABLE', 'Plugin system not initialized');
|
|
839
692
|
return;
|
|
840
693
|
}
|
|
841
694
|
// Build resource identifier (kind.group/name or kind/name for core resources)
|
|
842
695
|
const resourceType = apiGroup ? `${kind.toLowerCase()}.${apiGroup}` : kind.toLowerCase();
|
|
843
696
|
const resourceId = `${resourceType}/${name}`;
|
|
844
|
-
|
|
697
|
+
// PRD #359: Use unified plugin registry for kubectl operations
|
|
698
|
+
const pluginResponse = await (0, plugin_registry_1.invokePluginTool)('agentic-tools', 'kubectl_get_resource_json', {
|
|
845
699
|
resource: resourceId,
|
|
846
700
|
namespace: namespace
|
|
847
701
|
});
|
|
@@ -925,9 +779,9 @@ class RestApiRouter {
|
|
|
925
779
|
return;
|
|
926
780
|
}
|
|
927
781
|
this.logger.info('Processing get events request', { requestId, name, kind, namespace, uid });
|
|
928
|
-
// PRD #
|
|
929
|
-
if (!
|
|
930
|
-
await this.sendErrorResponse(res, requestId, HttpStatus.SERVICE_UNAVAILABLE, 'PLUGIN_UNAVAILABLE', '
|
|
782
|
+
// PRD #359: Use unified plugin registry
|
|
783
|
+
if (!(0, plugin_registry_1.isPluginInitialized)()) {
|
|
784
|
+
await this.sendErrorResponse(res, requestId, HttpStatus.SERVICE_UNAVAILABLE, 'PLUGIN_UNAVAILABLE', 'Plugin system not initialized');
|
|
931
785
|
return;
|
|
932
786
|
}
|
|
933
787
|
// Build field selector for involvedObject filtering
|
|
@@ -938,11 +792,12 @@ class RestApiRouter {
|
|
|
938
792
|
if (uid) {
|
|
939
793
|
fieldSelectors.push(`involvedObject.uid=${uid}`);
|
|
940
794
|
}
|
|
941
|
-
|
|
795
|
+
// PRD #359: Use unified plugin registry for kubectl operations
|
|
796
|
+
const pluginResponse = await (0, plugin_registry_1.invokePluginTool)('agentic-tools', 'kubectl_events', {
|
|
942
797
|
namespace: namespace,
|
|
943
798
|
args: [`--field-selector=${fieldSelectors.join(',')}`]
|
|
944
799
|
});
|
|
945
|
-
|
|
800
|
+
const events = [];
|
|
946
801
|
if (pluginResponse.success && pluginResponse.result) {
|
|
947
802
|
const pluginResult = pluginResponse.result;
|
|
948
803
|
if (pluginResult.success && pluginResult.data) {
|
|
@@ -1025,9 +880,9 @@ class RestApiRouter {
|
|
|
1025
880
|
}
|
|
1026
881
|
}
|
|
1027
882
|
this.logger.info('Processing get logs request', { requestId, name, namespace, container, tailLines });
|
|
1028
|
-
// PRD #
|
|
1029
|
-
if (!
|
|
1030
|
-
await this.sendErrorResponse(res, requestId, HttpStatus.SERVICE_UNAVAILABLE, 'PLUGIN_UNAVAILABLE', '
|
|
883
|
+
// PRD #359: Use unified plugin registry
|
|
884
|
+
if (!(0, plugin_registry_1.isPluginInitialized)()) {
|
|
885
|
+
await this.sendErrorResponse(res, requestId, HttpStatus.SERVICE_UNAVAILABLE, 'PLUGIN_UNAVAILABLE', 'Plugin system not initialized');
|
|
1031
886
|
return;
|
|
1032
887
|
}
|
|
1033
888
|
// Build args for kubectl_logs
|
|
@@ -1038,7 +893,8 @@ class RestApiRouter {
|
|
|
1038
893
|
if (container) {
|
|
1039
894
|
args.push('-c', container);
|
|
1040
895
|
}
|
|
1041
|
-
|
|
896
|
+
// PRD #359: Use unified plugin registry for kubectl operations
|
|
897
|
+
const pluginResponse = await (0, plugin_registry_1.invokePluginTool)('agentic-tools', 'kubectl_logs', {
|
|
1042
898
|
resource: name,
|
|
1043
899
|
namespace: namespace,
|
|
1044
900
|
args: args.length > 0 ? args : undefined
|
|
@@ -1119,7 +975,8 @@ class RestApiRouter {
|
|
|
1119
975
|
async handlePromptsGetRequest(req, res, requestId, promptName, body) {
|
|
1120
976
|
try {
|
|
1121
977
|
this.logger.info('Processing prompt get request', { requestId, promptName });
|
|
1122
|
-
const
|
|
978
|
+
const bodyObj = body;
|
|
979
|
+
const result = await (0, prompts_1.handlePromptsGetRequest)({ name: promptName, arguments: bodyObj?.arguments }, this.logger, requestId);
|
|
1123
980
|
const response = {
|
|
1124
981
|
success: true,
|
|
1125
982
|
data: result,
|
|
@@ -1210,29 +1067,31 @@ class RestApiRouter {
|
|
|
1210
1067
|
return;
|
|
1211
1068
|
}
|
|
1212
1069
|
// PRD #320: Select prompt based on tool name (defaults to 'query' for backwards compatibility)
|
|
1213
|
-
const toolName = primarySession.data.toolName || 'query';
|
|
1070
|
+
const toolName = (primarySession.data.toolName || 'query');
|
|
1214
1071
|
const promptName = (0, visualization_1.getPromptForTool)(toolName);
|
|
1215
1072
|
this.logger.info('Loading visualization prompt', { requestId, sessionIds, toolName, promptName });
|
|
1216
1073
|
// Load system prompt with session context
|
|
1217
1074
|
// PRD #320: Unified visualization prompt with tool-aware data selection
|
|
1218
1075
|
let intent;
|
|
1219
1076
|
let data;
|
|
1077
|
+
// Cast to allow access to tool-specific properties
|
|
1078
|
+
const sessionData = primarySession.data;
|
|
1220
1079
|
switch (toolName) {
|
|
1221
1080
|
case 'recommend':
|
|
1222
|
-
intent =
|
|
1081
|
+
intent = sessionData.intent || '';
|
|
1223
1082
|
data = isMultiSession ? sessions.map(s => s.data) : primarySession.data;
|
|
1224
1083
|
break;
|
|
1225
1084
|
case 'remediate':
|
|
1226
|
-
intent =
|
|
1227
|
-
data =
|
|
1085
|
+
intent = sessionData.issue || '';
|
|
1086
|
+
data = sessionData.finalAnalysis || primarySession.data;
|
|
1228
1087
|
break;
|
|
1229
1088
|
case 'operate':
|
|
1230
|
-
intent =
|
|
1089
|
+
intent = sessionData.intent || '';
|
|
1231
1090
|
data = primarySession.data;
|
|
1232
1091
|
break;
|
|
1233
1092
|
case 'version':
|
|
1234
1093
|
// PRD #320: Version tool provides system health status
|
|
1235
|
-
intent = `System health: ${
|
|
1094
|
+
intent = `System health: ${sessionData.summary?.overall || 'unknown'}`;
|
|
1236
1095
|
data = primarySession.data;
|
|
1237
1096
|
break;
|
|
1238
1097
|
default:
|
|
@@ -1423,12 +1282,337 @@ class RestApiRouter {
|
|
|
1423
1282
|
await this.sendErrorResponse(res, requestId, HttpStatus.INTERNAL_SERVER_ERROR, 'SESSION_RETRIEVAL_ERROR', 'Failed to retrieve session', { error: errorMessage });
|
|
1424
1283
|
}
|
|
1425
1284
|
}
|
|
1285
|
+
/**
|
|
1286
|
+
* Handle DELETE /api/v1/knowledge/source/:sourceIdentifier (PRD #356)
|
|
1287
|
+
* Delete all knowledge base chunks for a source identifier
|
|
1288
|
+
* Used by controller for GitKnowledgeSource cleanup
|
|
1289
|
+
*/
|
|
1290
|
+
async handleDeleteKnowledgeSource(req, res, requestId, sourceIdentifier) {
|
|
1291
|
+
try {
|
|
1292
|
+
// URL-decode the sourceIdentifier (it may contain / encoded as %2F)
|
|
1293
|
+
const decodedSourceIdentifier = decodeURIComponent(sourceIdentifier);
|
|
1294
|
+
this.logger.info('Processing delete knowledge source request', {
|
|
1295
|
+
requestId,
|
|
1296
|
+
sourceIdentifier: decodedSourceIdentifier,
|
|
1297
|
+
});
|
|
1298
|
+
// Check plugin availability
|
|
1299
|
+
if (!(0, plugin_registry_1.isPluginInitialized)()) {
|
|
1300
|
+
await this.sendErrorResponse(res, requestId, HttpStatus.SERVICE_UNAVAILABLE, 'PLUGIN_UNAVAILABLE', 'Plugin system not initialized');
|
|
1301
|
+
return;
|
|
1302
|
+
}
|
|
1303
|
+
const KNOWLEDGE_COLLECTION = 'knowledge-base';
|
|
1304
|
+
const PLUGIN_NAME = 'agentic-tools';
|
|
1305
|
+
// Step 1: Query all chunks matching the sourceIdentifier in metadata
|
|
1306
|
+
const queryResponse = await (0, plugin_registry_1.invokePluginTool)(PLUGIN_NAME, 'vector_query', {
|
|
1307
|
+
collection: KNOWLEDGE_COLLECTION,
|
|
1308
|
+
filter: {
|
|
1309
|
+
must: [{ key: 'metadata.sourceIdentifier', match: { value: decodedSourceIdentifier } }],
|
|
1310
|
+
},
|
|
1311
|
+
limit: 10000, // High limit to get all chunks for a source
|
|
1312
|
+
});
|
|
1313
|
+
if (!queryResponse.success) {
|
|
1314
|
+
const error = queryResponse.error;
|
|
1315
|
+
const errorMessage = error?.message || error?.error || 'Query failed';
|
|
1316
|
+
// If collection doesn't exist (Not Found), return success with 0 deleted
|
|
1317
|
+
if (errorMessage.includes('Not Found') || errorMessage.includes('not found')) {
|
|
1318
|
+
this.logger.info('Collection not found - returning success with 0 deleted', {
|
|
1319
|
+
requestId,
|
|
1320
|
+
sourceIdentifier: decodedSourceIdentifier,
|
|
1321
|
+
});
|
|
1322
|
+
const response = {
|
|
1323
|
+
success: true,
|
|
1324
|
+
data: {
|
|
1325
|
+
sourceIdentifier: decodedSourceIdentifier,
|
|
1326
|
+
chunksDeleted: 0,
|
|
1327
|
+
},
|
|
1328
|
+
meta: {
|
|
1329
|
+
timestamp: new Date().toISOString(),
|
|
1330
|
+
requestId,
|
|
1331
|
+
version: this.config.version,
|
|
1332
|
+
},
|
|
1333
|
+
};
|
|
1334
|
+
await this.sendJsonResponse(res, HttpStatus.OK, response);
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
1337
|
+
this.logger.error('Plugin query failed', new Error(errorMessage), {
|
|
1338
|
+
requestId,
|
|
1339
|
+
sourceIdentifier: decodedSourceIdentifier,
|
|
1340
|
+
});
|
|
1341
|
+
await this.sendErrorResponse(res, requestId, HttpStatus.INTERNAL_SERVER_ERROR, 'DELETE_SOURCE_ERROR', 'Failed to query chunks for deletion', { error: errorMessage });
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
// Extract results from plugin response
|
|
1345
|
+
const queryResult = queryResponse.result;
|
|
1346
|
+
if (!queryResult.success) {
|
|
1347
|
+
const errorMessage = queryResult.error || queryResult.message;
|
|
1348
|
+
// If collection doesn't exist, return success with 0 deleted
|
|
1349
|
+
if (errorMessage.includes('Not Found') || errorMessage.includes('not found')) {
|
|
1350
|
+
this.logger.info('Collection not found - returning success with 0 deleted', {
|
|
1351
|
+
requestId,
|
|
1352
|
+
sourceIdentifier: decodedSourceIdentifier,
|
|
1353
|
+
});
|
|
1354
|
+
const response = {
|
|
1355
|
+
success: true,
|
|
1356
|
+
data: {
|
|
1357
|
+
sourceIdentifier: decodedSourceIdentifier,
|
|
1358
|
+
chunksDeleted: 0,
|
|
1359
|
+
},
|
|
1360
|
+
meta: {
|
|
1361
|
+
timestamp: new Date().toISOString(),
|
|
1362
|
+
requestId,
|
|
1363
|
+
version: this.config.version,
|
|
1364
|
+
},
|
|
1365
|
+
};
|
|
1366
|
+
await this.sendJsonResponse(res, HttpStatus.OK, response);
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
await this.sendErrorResponse(res, requestId, HttpStatus.INTERNAL_SERVER_ERROR, 'DELETE_SOURCE_ERROR', 'Failed to query chunks for deletion', { error: errorMessage });
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
const chunksToDelete = queryResult.data || [];
|
|
1373
|
+
// If no chunks found, return success with 0 deleted
|
|
1374
|
+
if (chunksToDelete.length === 0) {
|
|
1375
|
+
this.logger.info('No chunks found for source identifier', {
|
|
1376
|
+
requestId,
|
|
1377
|
+
sourceIdentifier: decodedSourceIdentifier,
|
|
1378
|
+
});
|
|
1379
|
+
const response = {
|
|
1380
|
+
success: true,
|
|
1381
|
+
data: {
|
|
1382
|
+
sourceIdentifier: decodedSourceIdentifier,
|
|
1383
|
+
chunksDeleted: 0,
|
|
1384
|
+
},
|
|
1385
|
+
meta: {
|
|
1386
|
+
timestamp: new Date().toISOString(),
|
|
1387
|
+
requestId,
|
|
1388
|
+
version: this.config.version,
|
|
1389
|
+
},
|
|
1390
|
+
};
|
|
1391
|
+
await this.sendJsonResponse(res, HttpStatus.OK, response);
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
// Step 2: Delete each chunk by ID
|
|
1395
|
+
let deletedCount = 0;
|
|
1396
|
+
for (const chunk of chunksToDelete) {
|
|
1397
|
+
this.logger.debug('Deleting chunk', { requestId, chunkId: chunk.id });
|
|
1398
|
+
const deleteResponse = await (0, plugin_registry_1.invokePluginTool)(PLUGIN_NAME, 'vector_delete', {
|
|
1399
|
+
collection: KNOWLEDGE_COLLECTION,
|
|
1400
|
+
id: chunk.id,
|
|
1401
|
+
});
|
|
1402
|
+
if (!deleteResponse.success) {
|
|
1403
|
+
const error = deleteResponse.error;
|
|
1404
|
+
const errorMessage = error?.message || 'Delete failed';
|
|
1405
|
+
this.logger.error('Failed to delete chunk', new Error(errorMessage), {
|
|
1406
|
+
requestId,
|
|
1407
|
+
chunkId: chunk.id,
|
|
1408
|
+
deletedSoFar: deletedCount,
|
|
1409
|
+
});
|
|
1410
|
+
await this.sendErrorResponse(res, requestId, HttpStatus.INTERNAL_SERVER_ERROR, 'DELETE_SOURCE_ERROR', 'Failed to delete chunk', { chunkId: chunk.id, error: errorMessage, chunksDeletedBeforeFailure: deletedCount });
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
deletedCount++;
|
|
1414
|
+
}
|
|
1415
|
+
this.logger.info('Delete knowledge source operation completed', {
|
|
1416
|
+
requestId,
|
|
1417
|
+
sourceIdentifier: decodedSourceIdentifier,
|
|
1418
|
+
chunksDeleted: deletedCount,
|
|
1419
|
+
});
|
|
1420
|
+
const response = {
|
|
1421
|
+
success: true,
|
|
1422
|
+
data: {
|
|
1423
|
+
sourceIdentifier: decodedSourceIdentifier,
|
|
1424
|
+
chunksDeleted: deletedCount,
|
|
1425
|
+
},
|
|
1426
|
+
meta: {
|
|
1427
|
+
timestamp: new Date().toISOString(),
|
|
1428
|
+
requestId,
|
|
1429
|
+
version: this.config.version,
|
|
1430
|
+
},
|
|
1431
|
+
};
|
|
1432
|
+
await this.sendJsonResponse(res, HttpStatus.OK, response);
|
|
1433
|
+
}
|
|
1434
|
+
catch (error) {
|
|
1435
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1436
|
+
this.logger.error('Delete knowledge source request failed', error instanceof Error ? error : new Error(String(error)), {
|
|
1437
|
+
requestId,
|
|
1438
|
+
sourceIdentifier,
|
|
1439
|
+
});
|
|
1440
|
+
await this.sendErrorResponse(res, requestId, HttpStatus.INTERNAL_SERVER_ERROR, 'DELETE_SOURCE_ERROR', 'Failed to delete knowledge source', { error: errorMessage });
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
/**
|
|
1444
|
+
* Handle POST /api/v1/knowledge/ask (PRD #356)
|
|
1445
|
+
* Ask a question and receive an AI-synthesized answer from the knowledge base.
|
|
1446
|
+
* Uses an agentic approach with toolLoop to allow multiple searches if needed.
|
|
1447
|
+
*/
|
|
1448
|
+
async handleKnowledgeAsk(req, res, requestId, body) {
|
|
1449
|
+
try {
|
|
1450
|
+
// Validate request body
|
|
1451
|
+
if (!body || typeof body !== 'object') {
|
|
1452
|
+
await this.sendErrorResponse(res, requestId, HttpStatus.BAD_REQUEST, 'BAD_REQUEST', 'Request body must be a JSON object');
|
|
1453
|
+
return;
|
|
1454
|
+
}
|
|
1455
|
+
const { query, limit = 20, uriFilter } = body;
|
|
1456
|
+
// Validate limit parameter (must be positive integer)
|
|
1457
|
+
if (typeof limit !== 'number' || !Number.isInteger(limit) || limit < 1) {
|
|
1458
|
+
await this.sendErrorResponse(res, requestId, HttpStatus.BAD_REQUEST, 'INVALID_PARAMETER', 'The "limit" parameter must be a positive integer');
|
|
1459
|
+
return;
|
|
1460
|
+
}
|
|
1461
|
+
// Validate required query parameter
|
|
1462
|
+
if (!query || typeof query !== 'string' || query.trim().length === 0) {
|
|
1463
|
+
await this.sendErrorResponse(res, requestId, HttpStatus.BAD_REQUEST, 'BAD_REQUEST', 'Missing or empty required parameter: query');
|
|
1464
|
+
return;
|
|
1465
|
+
}
|
|
1466
|
+
this.logger.info('Processing knowledge ask request', {
|
|
1467
|
+
requestId,
|
|
1468
|
+
queryLength: query.length,
|
|
1469
|
+
limit,
|
|
1470
|
+
hasUriFilter: !!uriFilter,
|
|
1471
|
+
});
|
|
1472
|
+
// Check plugin availability (needed for search)
|
|
1473
|
+
if (!(0, plugin_registry_1.isPluginInitialized)()) {
|
|
1474
|
+
await this.sendErrorResponse(res, requestId, HttpStatus.SERVICE_UNAVAILABLE, 'PLUGIN_UNAVAILABLE', 'Plugin system not initialized');
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
// Check AI provider availability
|
|
1478
|
+
const aiProvider = (0, ai_provider_factory_1.createAIProvider)();
|
|
1479
|
+
if (!aiProvider.isInitialized()) {
|
|
1480
|
+
await this.sendErrorResponse(res, requestId, HttpStatus.SERVICE_UNAVAILABLE, 'AI_NOT_CONFIGURED', 'AI provider not configured. Set ANTHROPIC_API_KEY or another provider API key.');
|
|
1481
|
+
return;
|
|
1482
|
+
}
|
|
1483
|
+
// Define the search tool for the AI
|
|
1484
|
+
const searchTool = {
|
|
1485
|
+
name: 'search_knowledge_base',
|
|
1486
|
+
description: 'Search the knowledge base for relevant information. Returns chunks of text from documents that match the query semantically.',
|
|
1487
|
+
inputSchema: {
|
|
1488
|
+
type: 'object',
|
|
1489
|
+
properties: {
|
|
1490
|
+
query: {
|
|
1491
|
+
type: 'string',
|
|
1492
|
+
description: 'The search query - can be a question or keywords',
|
|
1493
|
+
},
|
|
1494
|
+
},
|
|
1495
|
+
required: ['query'],
|
|
1496
|
+
},
|
|
1497
|
+
};
|
|
1498
|
+
// Collect all chunks from search results across iterations
|
|
1499
|
+
const allChunks = [];
|
|
1500
|
+
// Tool executor that calls searchKnowledgeBase
|
|
1501
|
+
const toolExecutor = async (toolName, input) => {
|
|
1502
|
+
if (toolName !== 'search_knowledge_base') {
|
|
1503
|
+
return {
|
|
1504
|
+
success: false,
|
|
1505
|
+
error: `Unknown tool: ${toolName}`,
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1508
|
+
const searchInput = input;
|
|
1509
|
+
const result = await (0, manage_knowledge_1.searchKnowledgeBase)({
|
|
1510
|
+
query: searchInput.query,
|
|
1511
|
+
limit,
|
|
1512
|
+
uriFilter,
|
|
1513
|
+
});
|
|
1514
|
+
if (!result.success) {
|
|
1515
|
+
return {
|
|
1516
|
+
success: false,
|
|
1517
|
+
error: result.error,
|
|
1518
|
+
message: 'Search failed',
|
|
1519
|
+
};
|
|
1520
|
+
}
|
|
1521
|
+
// Collect chunks for the response
|
|
1522
|
+
for (const chunk of result.chunks) {
|
|
1523
|
+
// Avoid duplicates (same id)
|
|
1524
|
+
if (!allChunks.some(c => c.uri === chunk.uri && c.chunkIndex === chunk.chunkIndex)) {
|
|
1525
|
+
allChunks.push({
|
|
1526
|
+
content: chunk.content,
|
|
1527
|
+
uri: chunk.uri,
|
|
1528
|
+
score: chunk.score,
|
|
1529
|
+
chunkIndex: chunk.chunkIndex,
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
// Return results to the AI
|
|
1534
|
+
if (result.chunks.length === 0) {
|
|
1535
|
+
return {
|
|
1536
|
+
success: true,
|
|
1537
|
+
message: 'No matching documents found in the knowledge base.',
|
|
1538
|
+
chunks: [],
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
return {
|
|
1542
|
+
success: true,
|
|
1543
|
+
message: `Found ${result.chunks.length} relevant chunks.`,
|
|
1544
|
+
chunks: result.chunks.map(c => ({
|
|
1545
|
+
content: c.content,
|
|
1546
|
+
uri: c.uri,
|
|
1547
|
+
score: c.score,
|
|
1548
|
+
})),
|
|
1549
|
+
};
|
|
1550
|
+
};
|
|
1551
|
+
// Load system prompt
|
|
1552
|
+
const systemPrompt = (0, shared_prompt_loader_1.loadPrompt)('knowledge-ask');
|
|
1553
|
+
// Execute tool loop
|
|
1554
|
+
this.logger.info('Starting knowledge ask tool loop', { requestId });
|
|
1555
|
+
const result = await aiProvider.toolLoop({
|
|
1556
|
+
systemPrompt,
|
|
1557
|
+
userMessage: query,
|
|
1558
|
+
tools: [searchTool],
|
|
1559
|
+
toolExecutor,
|
|
1560
|
+
maxIterations: 5,
|
|
1561
|
+
operation: 'knowledge-ask',
|
|
1562
|
+
evaluationContext: {
|
|
1563
|
+
user_intent: query,
|
|
1564
|
+
},
|
|
1565
|
+
});
|
|
1566
|
+
this.logger.info('Knowledge ask tool loop completed', {
|
|
1567
|
+
requestId,
|
|
1568
|
+
iterations: result.iterations,
|
|
1569
|
+
chunksCollected: allChunks.length,
|
|
1570
|
+
toolCallsCount: result.toolCallsExecuted.length,
|
|
1571
|
+
});
|
|
1572
|
+
// Deduplicate sources from collected chunks
|
|
1573
|
+
const sourceMap = new Map();
|
|
1574
|
+
for (const chunk of allChunks) {
|
|
1575
|
+
if (!sourceMap.has(chunk.uri)) {
|
|
1576
|
+
sourceMap.set(chunk.uri, { uri: chunk.uri });
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
const sources = Array.from(sourceMap.values());
|
|
1580
|
+
// Build response
|
|
1581
|
+
const response = {
|
|
1582
|
+
success: true,
|
|
1583
|
+
data: {
|
|
1584
|
+
answer: result.finalMessage,
|
|
1585
|
+
sources,
|
|
1586
|
+
chunks: allChunks,
|
|
1587
|
+
},
|
|
1588
|
+
meta: {
|
|
1589
|
+
timestamp: new Date().toISOString(),
|
|
1590
|
+
requestId,
|
|
1591
|
+
version: this.config.version,
|
|
1592
|
+
},
|
|
1593
|
+
};
|
|
1594
|
+
await this.sendJsonResponse(res, HttpStatus.OK, response);
|
|
1595
|
+
this.logger.info('Knowledge ask completed', {
|
|
1596
|
+
requestId,
|
|
1597
|
+
sourcesFound: sources.length,
|
|
1598
|
+
chunksReturned: allChunks.length,
|
|
1599
|
+
answerLength: result.finalMessage.length,
|
|
1600
|
+
});
|
|
1601
|
+
}
|
|
1602
|
+
catch (error) {
|
|
1603
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1604
|
+
this.logger.error('Knowledge ask request failed', error instanceof Error ? error : new Error(String(error)), {
|
|
1605
|
+
requestId,
|
|
1606
|
+
});
|
|
1607
|
+
await this.sendErrorResponse(res, requestId, HttpStatus.INTERNAL_SERVER_ERROR, 'SYNTHESIS_ERROR', 'Failed to process knowledge ask request', { error: errorMessage });
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1426
1610
|
/**
|
|
1427
1611
|
* Set CORS headers
|
|
1428
1612
|
*/
|
|
1429
1613
|
setCorsHeaders(res) {
|
|
1430
1614
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
1431
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
1615
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
|
|
1432
1616
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
1433
1617
|
res.setHeader('Access-Control-Max-Age', '86400');
|
|
1434
1618
|
}
|
|
@@ -1476,5 +1660,12 @@ class RestApiRouter {
|
|
|
1476
1660
|
getConfig() {
|
|
1477
1661
|
return { ...this.config };
|
|
1478
1662
|
}
|
|
1663
|
+
/**
|
|
1664
|
+
* Get the route registry for OpenAPI generation and fixture validation
|
|
1665
|
+
* PRD #354: Exposes route registry for downstream consumers
|
|
1666
|
+
*/
|
|
1667
|
+
getRouteRegistry() {
|
|
1668
|
+
return this.routeRegistry;
|
|
1669
|
+
}
|
|
1479
1670
|
}
|
|
1480
1671
|
exports.RestApiRouter = RestApiRouter;
|