vme-mcp-server 0.1.5 → 0.1.7
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/lib/api-utils.js +251 -0
- package/dist/lib/capability-cache.js +131 -0
- package/dist/lib/capability-discovery.js +240 -0
- package/dist/lib/intent-recognition.js +159 -0
- package/dist/lib/name-resolver.js +174 -0
- package/dist/lib/session.js +59 -0
- package/dist/lib/vm-parsing.js +82 -0
- package/dist/server.js +36 -918
- package/dist/server_old.js +933 -0
- package/dist/tools/check-capability.js +84 -0
- package/dist/tools/create-vm.js +253 -0
- package/dist/tools/discover-capabilities.js +79 -0
- package/dist/tools/export-training-data.js +115 -0
- package/dist/tools/get-cache-status.js +123 -0
- package/dist/tools/get-resources.js +40 -0
- package/dist/tools/index.js +68 -0
- package/dist/tools/parse-intent.js +52 -0
- package/dist/tools/provide-feedback.js +61 -0
- package/dist/tools/query-resources.js +248 -0
- package/dist/types/interfaces.js +2 -0
- package/package.json +4 -4
@@ -0,0 +1,251 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
exports.getResources = exports.resolveInput = exports.getClusterNodes = exports.api = void 0;
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
8
|
+
// Setup axios instance for VME API
|
9
|
+
exports.api = axios_1.default.create({
|
10
|
+
baseURL: process.env.VME_API_BASE_URL,
|
11
|
+
headers: {
|
12
|
+
Authorization: `Bearer ${process.env.VME_API_TOKEN}`,
|
13
|
+
"Content-Type": "application/json"
|
14
|
+
}
|
15
|
+
});
|
16
|
+
// Helper function to get available cluster nodes
|
17
|
+
async function getClusterNodes() {
|
18
|
+
try {
|
19
|
+
const clusters = await exports.api.get("/clusters");
|
20
|
+
const prodCluster = clusters.data.clusters.find((cluster) => cluster.name === "prod01");
|
21
|
+
if (prodCluster && prodCluster.servers) {
|
22
|
+
return prodCluster.servers.map((server) => server.id);
|
23
|
+
}
|
24
|
+
}
|
25
|
+
catch (error) {
|
26
|
+
console.error('Error getting cluster nodes:', error);
|
27
|
+
}
|
28
|
+
// Fallback to known nodes if API fails
|
29
|
+
return [1, 2, 3];
|
30
|
+
}
|
31
|
+
exports.getClusterNodes = getClusterNodes;
|
32
|
+
// Resolve user input parameters to VME API resource IDs
|
33
|
+
async function resolveInput({ group, cloud, template, size }) {
|
34
|
+
const [groups, zones, instanceTypes, servicePlans] = await Promise.all([
|
35
|
+
exports.api.get("/groups"),
|
36
|
+
exports.api.get("/zones"),
|
37
|
+
exports.api.get("/instance-types"),
|
38
|
+
exports.api.get("/service-plans")
|
39
|
+
]);
|
40
|
+
// Find group (case-insensitive)
|
41
|
+
const foundGroup = groups.data.groups.find((g) => g.name.toLowerCase() === group.toLowerCase());
|
42
|
+
// Find zone/cloud (case-insensitive, handle both terms)
|
43
|
+
const foundZone = zones.data.zones.find((z) => z.name.toLowerCase() === cloud.toLowerCase());
|
44
|
+
// In HPE VM Essentials, all VMs use "HPE VM" instance type
|
45
|
+
// The actual OS is determined by the image selection
|
46
|
+
const instanceType = instanceTypes.data.instanceTypes.find((t) => t.name === 'HPE VM');
|
47
|
+
// Find appropriate image based on template using osType properties
|
48
|
+
let selectedImage;
|
49
|
+
const templateLower = template.toLowerCase();
|
50
|
+
// Get available images to find the right one
|
51
|
+
const images = await exports.api.get("/virtual-images");
|
52
|
+
if (templateLower.includes('ubuntu')) {
|
53
|
+
// Find Ubuntu images using osType.category and prefer latest version
|
54
|
+
const ubuntuImages = images.data.virtualImages.filter((img) => img.osType && img.osType.category === 'ubuntu').sort((a, b) => {
|
55
|
+
// Sort by version descending (24.04 > 22.04 > 20.04)
|
56
|
+
const aVersion = parseFloat(a.osType.osVersion || '0');
|
57
|
+
const bVersion = parseFloat(b.osType.osVersion || '0');
|
58
|
+
return bVersion - aVersion;
|
59
|
+
});
|
60
|
+
selectedImage = ubuntuImages[0];
|
61
|
+
}
|
62
|
+
else if (templateLower.includes('rocky')) {
|
63
|
+
// Find Rocky Linux images and prefer latest version (Rocky 9 > Rocky 8)
|
64
|
+
const rockyImages = images.data.virtualImages.filter((img) => img.osType && img.osType.category === 'rocky').sort((a, b) => {
|
65
|
+
const aVersion = parseInt(a.osType.osVersion || '0');
|
66
|
+
const bVersion = parseInt(b.osType.osVersion || '0');
|
67
|
+
return bVersion - aVersion; // Latest first
|
68
|
+
});
|
69
|
+
selectedImage = rockyImages[0];
|
70
|
+
}
|
71
|
+
else if (templateLower.includes('centos')) {
|
72
|
+
// Find CentOS images and prefer latest version (CentOS 9 > CentOS 8)
|
73
|
+
const centosImages = images.data.virtualImages.filter((img) => img.osType && img.osType.category === 'centos').sort((a, b) => {
|
74
|
+
const aVersion = parseInt(a.osType.osVersion || '0');
|
75
|
+
const bVersion = parseInt(b.osType.osVersion || '0');
|
76
|
+
return bVersion - aVersion; // Latest first
|
77
|
+
});
|
78
|
+
selectedImage = centosImages[0];
|
79
|
+
}
|
80
|
+
else if (templateLower.includes('debian')) {
|
81
|
+
// Find Debian images and prefer latest version (Debian 12 > Debian 11)
|
82
|
+
const debianImages = images.data.virtualImages.filter((img) => img.osType && img.osType.category === 'debian').sort((a, b) => {
|
83
|
+
const aVersion = parseInt(a.osType.osVersion || '0');
|
84
|
+
const bVersion = parseInt(b.osType.osVersion || '0');
|
85
|
+
return bVersion - aVersion; // Latest first
|
86
|
+
});
|
87
|
+
selectedImage = debianImages[0];
|
88
|
+
}
|
89
|
+
else if (templateLower.includes('alma')) {
|
90
|
+
// Find AlmaLinux images and prefer latest version (AlmaLinux 9 > AlmaLinux 8)
|
91
|
+
const almaImages = images.data.virtualImages.filter((img) => img.osType && img.osType.category === 'almalinux').sort((a, b) => {
|
92
|
+
const aVersion = parseInt(a.osType.osVersion || '0');
|
93
|
+
const bVersion = parseInt(b.osType.osVersion || '0');
|
94
|
+
return bVersion - aVersion; // Latest first
|
95
|
+
});
|
96
|
+
selectedImage = almaImages[0];
|
97
|
+
}
|
98
|
+
else if (templateLower.includes('rhel') || templateLower.includes('red hat')) {
|
99
|
+
// Find RHEL images and prefer latest version
|
100
|
+
const rhelImages = images.data.virtualImages.filter((img) => img.osType && img.osType.category === 'rhel').sort((a, b) => {
|
101
|
+
const aVersion = parseInt(a.osType.osVersion || '0');
|
102
|
+
const bVersion = parseInt(b.osType.osVersion || '0');
|
103
|
+
return bVersion - aVersion; // Latest first
|
104
|
+
});
|
105
|
+
selectedImage = rhelImages[0];
|
106
|
+
}
|
107
|
+
// Default to latest Ubuntu if no specific match
|
108
|
+
if (!selectedImage) {
|
109
|
+
const ubuntuImages = images.data.virtualImages.filter((img) => img.osType && img.osType.category === 'ubuntu').sort((a, b) => {
|
110
|
+
const aVersion = parseFloat(a.osType.osVersion || '0');
|
111
|
+
const bVersion = parseFloat(b.osType.osVersion || '0');
|
112
|
+
return bVersion - aVersion;
|
113
|
+
});
|
114
|
+
selectedImage = ubuntuImages[0] || images.data.virtualImages[0];
|
115
|
+
}
|
116
|
+
// Find service plan based on size (flexible matching)
|
117
|
+
let plan;
|
118
|
+
const sizeLower = size.toLowerCase();
|
119
|
+
if (sizeLower.includes('8gb') || sizeLower.includes('8 gb') || sizeLower.includes('medium')) {
|
120
|
+
plan = servicePlans.data.servicePlans.find((p) => p.name.includes('2 CPU, 8GB Memory'));
|
121
|
+
}
|
122
|
+
else if (sizeLower.includes('4gb') || sizeLower.includes('4 gb') || sizeLower.includes('small')) {
|
123
|
+
plan = servicePlans.data.servicePlans.find((p) => p.name.includes('1 CPU, 4GB Memory'));
|
124
|
+
}
|
125
|
+
// Fallback to any available plan
|
126
|
+
if (!plan) {
|
127
|
+
plan = servicePlans.data.servicePlans.find((p) => p.name.includes('CPU') && p.name.includes('Memory'));
|
128
|
+
}
|
129
|
+
return {
|
130
|
+
groupId: foundGroup?.id || groups.data.groups[0]?.id,
|
131
|
+
cloudId: foundZone?.id || zones.data.zones[0]?.id,
|
132
|
+
instanceTypeId: instanceType?.id,
|
133
|
+
servicePlanId: plan?.id,
|
134
|
+
imageId: selectedImage?.id,
|
135
|
+
// Return resolved names for better error reporting
|
136
|
+
resolvedGroup: foundGroup?.name || groups.data.groups[0]?.name,
|
137
|
+
resolvedCloud: foundZone?.name || zones.data.zones[0]?.name,
|
138
|
+
resolvedInstanceType: instanceType?.name,
|
139
|
+
resolvedPlan: plan?.name,
|
140
|
+
resolvedImage: selectedImage?.osType?.name || selectedImage?.name,
|
141
|
+
// Available options for error messages
|
142
|
+
availableGroups: groups.data.groups.map((g) => g.name),
|
143
|
+
availableZones: zones.data.zones.map((z) => z.name)
|
144
|
+
};
|
145
|
+
}
|
146
|
+
exports.resolveInput = resolveInput;
|
147
|
+
// Unified resource discovery function
|
148
|
+
async function getResources(type, intent, role) {
|
149
|
+
try {
|
150
|
+
// Fetch all resource types in parallel for comprehensive discovery
|
151
|
+
const [groups, zones, instanceTypes, servicePlans, virtualImages, clusters] = await Promise.all([
|
152
|
+
exports.api.get("/groups"),
|
153
|
+
exports.api.get("/zones"),
|
154
|
+
exports.api.get("/instance-types"),
|
155
|
+
exports.api.get("/service-plans"),
|
156
|
+
exports.api.get("/virtual-images"),
|
157
|
+
exports.api.get("/clusters")
|
158
|
+
]);
|
159
|
+
const allResources = {
|
160
|
+
groups: groups.data.groups || [],
|
161
|
+
zones: zones.data.zones || [],
|
162
|
+
instanceTypes: instanceTypes.data.instanceTypes || [],
|
163
|
+
servicePlans: servicePlans.data.servicePlans || [],
|
164
|
+
virtualImages: virtualImages.data.virtualImages || [],
|
165
|
+
clusters: clusters.data.clusters || []
|
166
|
+
};
|
167
|
+
// Apply intelligent filtering based on type
|
168
|
+
if (type) {
|
169
|
+
const typeLower = type.toLowerCase();
|
170
|
+
if (typeLower.includes('compute') || typeLower.includes('vm')) {
|
171
|
+
return {
|
172
|
+
groups: allResources.groups,
|
173
|
+
zones: allResources.zones,
|
174
|
+
servicePlans: allResources.servicePlans,
|
175
|
+
virtualImages: allResources.virtualImages,
|
176
|
+
clusters: allResources.clusters,
|
177
|
+
_filtered_for: 'compute/vm resources'
|
178
|
+
};
|
179
|
+
}
|
180
|
+
else if (typeLower.includes('network')) {
|
181
|
+
return {
|
182
|
+
zones: allResources.zones,
|
183
|
+
clusters: allResources.clusters,
|
184
|
+
_filtered_for: 'network resources'
|
185
|
+
};
|
186
|
+
}
|
187
|
+
else if (typeLower.includes('storage')) {
|
188
|
+
return {
|
189
|
+
servicePlans: allResources.servicePlans,
|
190
|
+
zones: allResources.zones,
|
191
|
+
_filtered_for: 'storage resources'
|
192
|
+
};
|
193
|
+
}
|
194
|
+
}
|
195
|
+
// Apply intent-based filtering
|
196
|
+
if (intent) {
|
197
|
+
const intentLower = intent.toLowerCase();
|
198
|
+
if (intentLower.includes('create') || intentLower.includes('provision')) {
|
199
|
+
// For creation intents, focus on templates and plans
|
200
|
+
return {
|
201
|
+
groups: allResources.groups,
|
202
|
+
zones: allResources.zones,
|
203
|
+
servicePlans: allResources.servicePlans,
|
204
|
+
virtualImages: allResources.virtualImages.map((img) => ({
|
205
|
+
id: img.id,
|
206
|
+
name: img.name,
|
207
|
+
osType: img.osType,
|
208
|
+
recommended: img.osType?.category === 'ubuntu' ? true : false
|
209
|
+
})),
|
210
|
+
_filtered_for: 'creation/provisioning intent'
|
211
|
+
};
|
212
|
+
}
|
213
|
+
else if (intentLower.includes('list') || intentLower.includes('show') || intentLower.includes('discover')) {
|
214
|
+
// For discovery intents, return comprehensive view
|
215
|
+
return {
|
216
|
+
summary: {
|
217
|
+
total_groups: allResources.groups.length,
|
218
|
+
total_zones: allResources.zones.length,
|
219
|
+
total_service_plans: allResources.servicePlans.length,
|
220
|
+
total_os_templates: allResources.virtualImages.length,
|
221
|
+
total_clusters: allResources.clusters.length
|
222
|
+
},
|
223
|
+
...allResources,
|
224
|
+
_filtered_for: 'discovery/listing intent'
|
225
|
+
};
|
226
|
+
}
|
227
|
+
}
|
228
|
+
// Default: return all resources with semantic organization
|
229
|
+
return {
|
230
|
+
compute: {
|
231
|
+
groups: allResources.groups,
|
232
|
+
servicePlans: allResources.servicePlans,
|
233
|
+
virtualImages: allResources.virtualImages
|
234
|
+
},
|
235
|
+
infrastructure: {
|
236
|
+
zones: allResources.zones,
|
237
|
+
clusters: allResources.clusters,
|
238
|
+
instanceTypes: allResources.instanceTypes
|
239
|
+
},
|
240
|
+
_organization: 'semantic grouping by function'
|
241
|
+
};
|
242
|
+
}
|
243
|
+
catch (error) {
|
244
|
+
return {
|
245
|
+
error: `Failed to fetch resources: ${error.response?.data?.message || error.message}`,
|
246
|
+
available_endpoints: ['/groups', '/zones', '/service-plans', '/virtual-images', '/clusters'],
|
247
|
+
fallback_suggestion: 'Try checking individual resource types with specific API endpoints'
|
248
|
+
};
|
249
|
+
}
|
250
|
+
}
|
251
|
+
exports.getResources = getResources;
|
@@ -0,0 +1,131 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.capabilityCache = exports.SmartCapabilityCache = void 0;
|
4
|
+
const api_utils_js_1 = require("./api-utils.js");
|
5
|
+
// Smart capability cache with field-level TTL
|
6
|
+
class SmartCapabilityCache {
|
7
|
+
constructor() {
|
8
|
+
this.cache = new Map();
|
9
|
+
// TTL configuration based on VME resource change patterns
|
10
|
+
this.ttlConfig = {
|
11
|
+
zones: 3600, // Zones rarely change (1 hour)
|
12
|
+
clusters: 1800, // Cluster topology changes occasionally (30 min)
|
13
|
+
service_plans: 1800, // Plans change occasionally (30 min)
|
14
|
+
instance_types: 7200, // Instance types very stable (2 hours)
|
15
|
+
virtual_images: 900, // Images updated more frequently (15 min)
|
16
|
+
cluster_status: 300, // Node status changes often (5 min)
|
17
|
+
resource_pools: 1800, // Pool config changes occasionally (30 min)
|
18
|
+
networks: 3600, // Network config rarely changes (1 hour)
|
19
|
+
groups: 3600, // Groups rarely change (1 hour)
|
20
|
+
};
|
21
|
+
// Default TTL for unknown fields
|
22
|
+
this.defaultTTL = 600; // 10 minutes
|
23
|
+
}
|
24
|
+
/**
|
25
|
+
* Get data for a specific field, using cache if fresh or fetching if stale
|
26
|
+
*/
|
27
|
+
async getField(fieldName, forceRefresh = false) {
|
28
|
+
const cached = this.cache.get(fieldName);
|
29
|
+
const now = Math.floor(Date.now() / 1000);
|
30
|
+
// Check if cache is fresh and not forcing refresh
|
31
|
+
if (!forceRefresh && cached && this.isFresh(cached, now)) {
|
32
|
+
return cached.data;
|
33
|
+
}
|
34
|
+
// Fetch fresh data from VME API
|
35
|
+
const endpoint = this.getEndpointForField(fieldName);
|
36
|
+
try {
|
37
|
+
const response = await api_utils_js_1.api.get(endpoint);
|
38
|
+
// Store in cache with appropriate TTL
|
39
|
+
const cacheEntry = {
|
40
|
+
data: response.data,
|
41
|
+
cached_at: now,
|
42
|
+
ttl_seconds: this.ttlConfig[fieldName] || this.defaultTTL,
|
43
|
+
endpoint: endpoint,
|
44
|
+
field_name: fieldName
|
45
|
+
};
|
46
|
+
this.cache.set(fieldName, cacheEntry);
|
47
|
+
return response.data;
|
48
|
+
}
|
49
|
+
catch (error) {
|
50
|
+
// If API fails and we have stale cache, return it with warning
|
51
|
+
if (cached) {
|
52
|
+
console.warn(`VME API failed for ${fieldName}, returning stale cache data`);
|
53
|
+
return cached.data;
|
54
|
+
}
|
55
|
+
// No cache and API failed - propagate error
|
56
|
+
throw new Error(`Failed to fetch ${fieldName}: ${error.message}`);
|
57
|
+
}
|
58
|
+
}
|
59
|
+
/**
|
60
|
+
* Get cache status for a specific field or all fields
|
61
|
+
*/
|
62
|
+
getCacheStatus(fieldName) {
|
63
|
+
const now = Math.floor(Date.now() / 1000);
|
64
|
+
const statuses = [];
|
65
|
+
if (fieldName) {
|
66
|
+
const cached = this.cache.get(fieldName);
|
67
|
+
if (cached) {
|
68
|
+
statuses.push(this.buildCacheStatus(cached, now));
|
69
|
+
}
|
70
|
+
}
|
71
|
+
else {
|
72
|
+
// Return status for all cached fields
|
73
|
+
for (const cached of this.cache.values()) {
|
74
|
+
statuses.push(this.buildCacheStatus(cached, now));
|
75
|
+
}
|
76
|
+
}
|
77
|
+
return statuses;
|
78
|
+
}
|
79
|
+
/**
|
80
|
+
* Invalidate cache for specific field or all fields
|
81
|
+
*/
|
82
|
+
invalidate(fieldName) {
|
83
|
+
if (fieldName) {
|
84
|
+
this.cache.delete(fieldName);
|
85
|
+
}
|
86
|
+
else {
|
87
|
+
this.cache.clear();
|
88
|
+
}
|
89
|
+
}
|
90
|
+
/**
|
91
|
+
* Get cache hit rate statistics
|
92
|
+
*/
|
93
|
+
getStatistics() {
|
94
|
+
// This would need hit/miss tracking - placeholder for now
|
95
|
+
return { hits: 0, misses: 0, hitRate: 0 };
|
96
|
+
}
|
97
|
+
// Private helper methods
|
98
|
+
isFresh(cached, now) {
|
99
|
+
return (now - cached.cached_at) < cached.ttl_seconds;
|
100
|
+
}
|
101
|
+
buildCacheStatus(cached, now) {
|
102
|
+
const age = now - cached.cached_at;
|
103
|
+
const expiresIn = cached.ttl_seconds - age;
|
104
|
+
return {
|
105
|
+
field_name: cached.field_name,
|
106
|
+
cached_at: cached.cached_at,
|
107
|
+
ttl_seconds: cached.ttl_seconds,
|
108
|
+
age_seconds: age,
|
109
|
+
is_fresh: this.isFresh(cached, now),
|
110
|
+
expires_in_seconds: Math.max(0, expiresIn),
|
111
|
+
endpoint: cached.endpoint
|
112
|
+
};
|
113
|
+
}
|
114
|
+
getEndpointForField(fieldName) {
|
115
|
+
// Map field names to VME API endpoints
|
116
|
+
const endpointMap = {
|
117
|
+
zones: "/zones",
|
118
|
+
clusters: "/clusters",
|
119
|
+
service_plans: "/service-plans",
|
120
|
+
instance_types: "/instance-types",
|
121
|
+
virtual_images: "/virtual-images",
|
122
|
+
resource_pools: "/resource-pools",
|
123
|
+
networks: "/networks",
|
124
|
+
groups: "/groups"
|
125
|
+
};
|
126
|
+
return endpointMap[fieldName] || `/${fieldName}`;
|
127
|
+
}
|
128
|
+
}
|
129
|
+
exports.SmartCapabilityCache = SmartCapabilityCache;
|
130
|
+
// Global cache instance
|
131
|
+
exports.capabilityCache = new SmartCapabilityCache();
|
@@ -0,0 +1,240 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.capabilityDiscovery = exports.CapabilityDiscoveryEngine = void 0;
|
4
|
+
const capability_cache_js_1 = require("./capability-cache.js");
|
5
|
+
// Capability discovery engine
|
6
|
+
class CapabilityDiscoveryEngine {
|
7
|
+
/**
|
8
|
+
* Discover capabilities for specified domains
|
9
|
+
*/
|
10
|
+
async discoverCapabilities(domains = ["all"], forceRefresh = false) {
|
11
|
+
const result = {
|
12
|
+
discovered_at: new Date().toISOString(),
|
13
|
+
cache_status: []
|
14
|
+
};
|
15
|
+
// Determine which domains to discover
|
16
|
+
const shouldDiscover = (domain) => domains.includes("all") || domains.includes(domain);
|
17
|
+
try {
|
18
|
+
// Discover capabilities in parallel for efficiency
|
19
|
+
const discoveries = await Promise.allSettled([
|
20
|
+
shouldDiscover("compute") ? this.discoverComputeCapabilities(forceRefresh) : Promise.resolve(null),
|
21
|
+
shouldDiscover("networking") ? this.discoverNetworkingCapabilities(forceRefresh) : Promise.resolve(null),
|
22
|
+
shouldDiscover("storage") ? this.discoverStorageCapabilities(forceRefresh) : Promise.resolve(null),
|
23
|
+
shouldDiscover("platform") ? this.discoverPlatformCapabilities(forceRefresh) : Promise.resolve(null),
|
24
|
+
]);
|
25
|
+
// Process results
|
26
|
+
if (discoveries[0]?.status === "fulfilled" && discoveries[0].value) {
|
27
|
+
result.compute = discoveries[0].value;
|
28
|
+
}
|
29
|
+
if (discoveries[1]?.status === "fulfilled" && discoveries[1].value) {
|
30
|
+
result.networking = discoveries[1].value;
|
31
|
+
}
|
32
|
+
if (discoveries[2]?.status === "fulfilled" && discoveries[2].value) {
|
33
|
+
result.storage = discoveries[2].value;
|
34
|
+
}
|
35
|
+
if (discoveries[3]?.status === "fulfilled" && discoveries[3].value) {
|
36
|
+
result.platform = discoveries[3].value;
|
37
|
+
}
|
38
|
+
// Add cache status summary
|
39
|
+
result.cache_status = this.getCacheStatusSummary();
|
40
|
+
return result;
|
41
|
+
}
|
42
|
+
catch (error) {
|
43
|
+
throw new Error(`Capability discovery failed: ${error.message}`);
|
44
|
+
}
|
45
|
+
}
|
46
|
+
/**
|
47
|
+
* Check if a specific capability is available
|
48
|
+
*/
|
49
|
+
async checkCapability(question, capabilityType) {
|
50
|
+
// Simple natural language processing for capability questions
|
51
|
+
const questionLower = question.toLowerCase();
|
52
|
+
try {
|
53
|
+
// GPU support check
|
54
|
+
if (questionLower.includes("gpu") || capabilityType === "gpu_support") {
|
55
|
+
const compute = await this.discoverComputeCapabilities();
|
56
|
+
return {
|
57
|
+
answer: compute.gpu_support,
|
58
|
+
details: compute.gpu_support
|
59
|
+
? "GPU support is available in this VME environment"
|
60
|
+
: "No GPU support detected in cluster nodes",
|
61
|
+
confidence: 0.95
|
62
|
+
};
|
63
|
+
}
|
64
|
+
// Hypervisor checks
|
65
|
+
if (questionLower.includes("vmware") || questionLower.includes("kvm") || questionLower.includes("hypervisor")) {
|
66
|
+
const compute = await this.discoverComputeCapabilities();
|
67
|
+
const hypervisor = questionLower.includes("vmware") ? "VMware" :
|
68
|
+
questionLower.includes("kvm") ? "KVM" : null;
|
69
|
+
if (hypervisor) {
|
70
|
+
const available = compute.hypervisors.includes(hypervisor);
|
71
|
+
return {
|
72
|
+
answer: available,
|
73
|
+
details: available
|
74
|
+
? `${hypervisor} hypervisor is available`
|
75
|
+
: `${hypervisor} hypervisor not found. Available: ${compute.hypervisors.join(", ")}`,
|
76
|
+
confidence: 0.9
|
77
|
+
};
|
78
|
+
}
|
79
|
+
}
|
80
|
+
// CPU/Memory limit checks
|
81
|
+
if (questionLower.match(/\b(\d+)\s*(cpu|core|vcpu)/)) {
|
82
|
+
const compute = await this.discoverComputeCapabilities();
|
83
|
+
const requestedCPU = parseInt(questionLower.match(/\b(\d+)\s*(cpu|core|vcpu)/)?.[1] || "0");
|
84
|
+
const canSupport = requestedCPU <= compute.max_cpu_per_vm;
|
85
|
+
return {
|
86
|
+
answer: canSupport,
|
87
|
+
details: canSupport
|
88
|
+
? `${requestedCPU} CPU configuration is supported (max: ${compute.max_cpu_per_vm})`
|
89
|
+
: `${requestedCPU} CPU exceeds maximum of ${compute.max_cpu_per_vm} per VM`,
|
90
|
+
confidence: 0.85
|
91
|
+
};
|
92
|
+
}
|
93
|
+
// Generic fallback
|
94
|
+
return {
|
95
|
+
answer: false,
|
96
|
+
details: "Unable to determine capability from question. Try being more specific.",
|
97
|
+
confidence: 0.1
|
98
|
+
};
|
99
|
+
}
|
100
|
+
catch (error) {
|
101
|
+
return {
|
102
|
+
answer: false,
|
103
|
+
details: `Error checking capability: ${error.message}`,
|
104
|
+
confidence: 0.0
|
105
|
+
};
|
106
|
+
}
|
107
|
+
}
|
108
|
+
// Private discovery methods for each domain
|
109
|
+
async discoverComputeCapabilities(forceRefresh = false) {
|
110
|
+
const [clusters, servicePlans, instanceTypes, virtualImages] = await Promise.all([
|
111
|
+
capability_cache_js_1.capabilityCache.getField("clusters", forceRefresh),
|
112
|
+
capability_cache_js_1.capabilityCache.getField("service_plans", forceRefresh),
|
113
|
+
capability_cache_js_1.capabilityCache.getField("instance_types", forceRefresh),
|
114
|
+
capability_cache_js_1.capabilityCache.getField("virtual_images", forceRefresh),
|
115
|
+
]);
|
116
|
+
// Derive hypervisors from cluster data
|
117
|
+
const hypervisors = new Set();
|
118
|
+
let maxCPU = 0;
|
119
|
+
let maxMemoryGB = 0;
|
120
|
+
let hasGPU = false;
|
121
|
+
if (clusters?.clusters) {
|
122
|
+
for (const cluster of clusters.clusters) {
|
123
|
+
if (cluster.servers) {
|
124
|
+
for (const server of cluster.servers) {
|
125
|
+
if (server.serverType) {
|
126
|
+
hypervisors.add(server.serverType);
|
127
|
+
}
|
128
|
+
if (server.maxCpu && server.maxCpu > maxCPU) {
|
129
|
+
maxCPU = server.maxCpu;
|
130
|
+
}
|
131
|
+
if (server.maxMemory && server.maxMemory > maxMemoryGB) {
|
132
|
+
maxMemoryGB = Math.floor(server.maxMemory / (1024 * 1024 * 1024)); // Convert to GB
|
133
|
+
}
|
134
|
+
if (server.gpuCount && server.gpuCount > 0) {
|
135
|
+
hasGPU = true;
|
136
|
+
}
|
137
|
+
}
|
138
|
+
}
|
139
|
+
}
|
140
|
+
}
|
141
|
+
// Process service plans for limits
|
142
|
+
const plans = servicePlans?.servicePlans?.map((plan) => ({
|
143
|
+
id: plan.id,
|
144
|
+
name: plan.name,
|
145
|
+
max_cpu: plan.maxCpu,
|
146
|
+
max_memory: plan.maxMemory,
|
147
|
+
max_storage: plan.maxStorage
|
148
|
+
})) || [];
|
149
|
+
// Process instance types
|
150
|
+
const instances = instanceTypes?.instanceTypes?.map((type) => ({
|
151
|
+
id: type.id,
|
152
|
+
name: type.name,
|
153
|
+
code: type.code
|
154
|
+
})) || [];
|
155
|
+
// Process virtual images
|
156
|
+
const images = virtualImages?.virtualImages?.map((img) => ({
|
157
|
+
id: img.id,
|
158
|
+
name: img.name,
|
159
|
+
os_type: img.osType?.name || "Unknown",
|
160
|
+
category: img.osType?.category || "unknown",
|
161
|
+
version: img.osType?.osVersion
|
162
|
+
})) || [];
|
163
|
+
return {
|
164
|
+
hypervisors: Array.from(hypervisors),
|
165
|
+
instance_types: instances,
|
166
|
+
service_plans: plans,
|
167
|
+
max_cpu_per_vm: maxCPU,
|
168
|
+
max_memory_per_vm: `${maxMemoryGB}GB`,
|
169
|
+
gpu_support: hasGPU,
|
170
|
+
available_images: images.slice(0, 10) // Limit to prevent token bloat
|
171
|
+
};
|
172
|
+
}
|
173
|
+
async discoverNetworkingCapabilities(forceRefresh = false) {
|
174
|
+
const [zones, networks] = await Promise.all([
|
175
|
+
capability_cache_js_1.capabilityCache.getField("zones", forceRefresh),
|
176
|
+
capability_cache_js_1.capabilityCache.getField("networks", forceRefresh).catch(() => null), // Optional
|
177
|
+
]);
|
178
|
+
const zoneList = zones?.zones?.map((zone) => ({
|
179
|
+
id: zone.id,
|
180
|
+
name: zone.name,
|
181
|
+
description: zone.description
|
182
|
+
})) || [];
|
183
|
+
return {
|
184
|
+
zones: zoneList,
|
185
|
+
network_types: ["VLAN"], // Default for VME
|
186
|
+
load_balancer_support: false, // Would need to check features
|
187
|
+
firewall_support: false, // Would need to check features
|
188
|
+
};
|
189
|
+
}
|
190
|
+
async discoverStorageCapabilities(forceRefresh = false) {
|
191
|
+
// Get real data from VME API
|
192
|
+
const servicePlans = await capability_cache_js_1.capabilityCache.getField("service_plans", forceRefresh);
|
193
|
+
// Derive storage capabilities from actual service plans and other data
|
194
|
+
const storageTypes = new Set();
|
195
|
+
let maxVolumeSize = "1TB"; // Default
|
196
|
+
if (servicePlans?.servicePlans) {
|
197
|
+
for (const plan of servicePlans.servicePlans) {
|
198
|
+
// Look for storage-related info in service plans
|
199
|
+
if (plan.maxStorage) {
|
200
|
+
const sizeGB = Math.floor(plan.maxStorage / (1024 * 1024 * 1024));
|
201
|
+
if (sizeGB > parseInt(maxVolumeSize)) {
|
202
|
+
maxVolumeSize = `${sizeGB}GB`;
|
203
|
+
}
|
204
|
+
}
|
205
|
+
}
|
206
|
+
}
|
207
|
+
// Basic storage types - would need more VME API endpoints to get real storage info
|
208
|
+
storageTypes.add("Local"); // VME always has local storage
|
209
|
+
return {
|
210
|
+
storage_types: Array.from(storageTypes),
|
211
|
+
max_volume_size: maxVolumeSize,
|
212
|
+
snapshot_support: true, // VME typically supports snapshots
|
213
|
+
encryption_support: false, // Would need to check actual VME features
|
214
|
+
};
|
215
|
+
}
|
216
|
+
async discoverPlatformCapabilities(forceRefresh = false) {
|
217
|
+
const groups = await capability_cache_js_1.capabilityCache.getField("groups", forceRefresh);
|
218
|
+
const groupList = groups?.groups?.map((group) => ({
|
219
|
+
id: group.id,
|
220
|
+
name: group.name,
|
221
|
+
description: group.description
|
222
|
+
})) || [];
|
223
|
+
return {
|
224
|
+
api_version: "6.2.x", // Would get from API info endpoint
|
225
|
+
groups: groupList,
|
226
|
+
enabled_features: ["VM Provisioning", "Resource Management"], // Would derive from license/features
|
227
|
+
};
|
228
|
+
}
|
229
|
+
getCacheStatusSummary() {
|
230
|
+
const statuses = capability_cache_js_1.capabilityCache.getCacheStatus();
|
231
|
+
return statuses.map(status => ({
|
232
|
+
field: status.field_name,
|
233
|
+
fresh: status.is_fresh,
|
234
|
+
age_seconds: status.age_seconds
|
235
|
+
}));
|
236
|
+
}
|
237
|
+
}
|
238
|
+
exports.CapabilityDiscoveryEngine = CapabilityDiscoveryEngine;
|
239
|
+
// Global discovery engine instance
|
240
|
+
exports.capabilityDiscovery = new CapabilityDiscoveryEngine();
|