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.
@@ -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();