@kwirthmagnify/kwirth-plugin-pinocchio 0.2.10 → 0.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/back.js +50 -573
  2. package/front.js +94 -404
  3. package/package.json +1 -1
package/back.js CHANGED
@@ -11262,544 +11262,8 @@ var kindsAvailable = ["Pod", "Deployment", "DaemonSet", "StatefulSet", "ReplicaS
11262
11262
 
11263
11263
  // src/back/index.ts
11264
11264
  var import_kwirth_common_ai = __toESM(require_kwirth_common_ai(), 1);
11265
- var import_back2 = __toESM(require_back(), 1);
11266
- var import_back3 = __toESM(require_back(), 1);
11267
-
11268
- // src/back/Tools.ts
11269
11265
  var import_back = __toESM(require_back(), 1);
11270
- var import_child_process = require("child_process");
11271
- var import_util = require("util");
11272
- var execAsync = (0, import_util.promisify)(import_child_process.exec);
11273
- function mapToJson(data) {
11274
- if (data instanceof Map) {
11275
- const obj = {};
11276
- for (const [key, value] of data.entries()) {
11277
- obj[String(key)] = mapToJson(value);
11278
- }
11279
- return obj;
11280
- }
11281
- if (Array.isArray(data)) {
11282
- return data.map(mapToJson);
11283
- }
11284
- if (data !== null && typeof data === "object") {
11285
- const newObj = {};
11286
- for (const key of Object.keys(data)) {
11287
- newObj[key] = mapToJson(data[key]);
11288
- }
11289
- return newObj;
11290
- }
11291
- return data;
11292
- }
11293
- var createTools = (context) => {
11294
- return {
11295
- // ── CLUSTER CONFIG ───────────────────────────────────────────────────
11296
- get_node_data: (0, import_back.tool)({
11297
- description: "Returns configuration info about all Kubernetes nodes (name, IP). Configuration only \u2014 not workload or usage data.",
11298
- inputSchema: import_back.z.object({}),
11299
- execute: async () => {
11300
- context.trace("get_node_data", {});
11301
- return mapToJson(context.nodes);
11302
- }
11303
- }),
11304
- get_cluster_data: (0, import_back.tool)({
11305
- description: "Returns general cluster info: name, flavour (AKS/EKS/GKE/k3s/k3d), total vCPUs, total memory, node count and readiness status.",
11306
- inputSchema: import_back.z.object({}),
11307
- execute: async () => {
11308
- context.trace("get_cluster_data", {});
11309
- try {
11310
- const resp = await context.clusterInfo.coreApi.listNode();
11311
- return {
11312
- name: context.clusterInfo.name,
11313
- flavour: context.clusterInfo.flavour,
11314
- vcpus: context.clusterInfo.vcpus,
11315
- memoryGB: Math.round(context.clusterInfo.memory / 1024 / 1024 / 1024 * 100) / 100,
11316
- nodeCount: resp.items.length,
11317
- nodes: resp.items.map((n) => ({
11318
- name: n.metadata?.name,
11319
- cpu: n.status?.capacity?.["cpu"],
11320
- memoryKi: n.status?.capacity?.["memory"],
11321
- ready: n.status?.conditions?.find((c) => c.type === "Ready")?.status === "True",
11322
- unschedulable: n.spec?.unschedulable ?? false
11323
- }))
11324
- };
11325
- } catch (err) {
11326
- return { error: err.message ?? String(err) };
11327
- }
11328
- }
11329
- }),
11330
- get_workload_data: (0, import_back.tool)({
11331
- description: "Returns all workloads in the cluster: deployments, statefulsets, daemonsets, pods and services. Optionally filter by namespace.",
11332
- inputSchema: import_back.z.object({
11333
- namespace: import_back.z.string().optional().describe("Namespace to filter results (omit for all namespaces)")
11334
- }),
11335
- execute: async ({ namespace }) => {
11336
- context.trace("get_workload_data", { namespace: namespace ?? "*" });
11337
- try {
11338
- const [deploymentsResp, statefulSetsResp, daemonSetsResp, podsResp, servicesResp] = await Promise.all([
11339
- namespace ? context.clusterInfo.appsApi.listNamespacedDeployment({ namespace }) : context.clusterInfo.appsApi.listDeploymentForAllNamespaces(),
11340
- namespace ? context.clusterInfo.appsApi.listNamespacedStatefulSet({ namespace }) : context.clusterInfo.appsApi.listStatefulSetForAllNamespaces(),
11341
- namespace ? context.clusterInfo.appsApi.listNamespacedDaemonSet({ namespace }) : context.clusterInfo.appsApi.listDaemonSetForAllNamespaces(),
11342
- namespace ? context.clusterInfo.coreApi.listNamespacedPod({ namespace }) : context.clusterInfo.coreApi.listPodForAllNamespaces(),
11343
- namespace ? context.clusterInfo.coreApi.listNamespacedService({ namespace }) : context.clusterInfo.coreApi.listServiceForAllNamespaces()
11344
- ]);
11345
- return {
11346
- deployments: deploymentsResp.items.map((d) => ({
11347
- name: d.metadata?.name,
11348
- namespace: d.metadata?.namespace,
11349
- replicas: d.spec?.replicas,
11350
- readyReplicas: d.status?.readyReplicas ?? 0,
11351
- availableReplicas: d.status?.availableReplicas ?? 0
11352
- })),
11353
- statefulSets: statefulSetsResp.items.map((s) => ({
11354
- name: s.metadata?.name,
11355
- namespace: s.metadata?.namespace,
11356
- replicas: s.spec?.replicas,
11357
- readyReplicas: s.status?.readyReplicas ?? 0
11358
- })),
11359
- daemonSets: daemonSetsResp.items.map((d) => ({
11360
- name: d.metadata?.name,
11361
- namespace: d.metadata?.namespace,
11362
- desired: d.status?.desiredNumberScheduled,
11363
- ready: d.status?.numberReady
11364
- })),
11365
- pods: podsResp.items.map((p) => ({
11366
- name: p.metadata?.name,
11367
- namespace: p.metadata?.namespace,
11368
- nodeName: p.spec?.nodeName,
11369
- phase: p.status?.phase,
11370
- ready: p.status?.conditions?.find((c) => c.type === "Ready")?.status === "True"
11371
- })),
11372
- services: servicesResp.items.map((s) => ({
11373
- name: s.metadata?.name,
11374
- namespace: s.metadata?.namespace,
11375
- type: s.spec?.type,
11376
- clusterIP: s.spec?.clusterIP
11377
- }))
11378
- };
11379
- } catch (err) {
11380
- return { error: err.message ?? String(err) };
11381
- }
11382
- }
11383
- }),
11384
- get_space_data: (0, import_back.tool)({
11385
- description: "Returns all resources in a specific Kubernetes namespace: pods (with restart count), deployments, services, configmap names.",
11386
- inputSchema: import_back.z.object({
11387
- namespace: import_back.z.string().describe("Name of the namespace to retrieve data for")
11388
- }),
11389
- execute: async ({ namespace }) => {
11390
- context.trace("get_space_data", { namespace });
11391
- try {
11392
- const [podsResp, deploymentsResp, servicesResp, configMapsResp] = await Promise.all([
11393
- context.clusterInfo.coreApi.listNamespacedPod({ namespace }),
11394
- context.clusterInfo.appsApi.listNamespacedDeployment({ namespace }),
11395
- context.clusterInfo.coreApi.listNamespacedService({ namespace }),
11396
- context.clusterInfo.coreApi.listNamespacedConfigMap({ namespace })
11397
- ]);
11398
- return {
11399
- namespace,
11400
- pods: podsResp.items.map((p) => ({
11401
- name: p.metadata?.name,
11402
- phase: p.status?.phase,
11403
- nodeName: p.spec?.nodeName,
11404
- ready: p.status?.conditions?.find((c) => c.type === "Ready")?.status === "True",
11405
- restartCount: p.status?.containerStatuses?.reduce((sum, cs) => sum + cs.restartCount, 0) ?? 0
11406
- })),
11407
- deployments: deploymentsResp.items.map((d) => ({
11408
- name: d.metadata?.name,
11409
- replicas: d.spec?.replicas,
11410
- readyReplicas: d.status?.readyReplicas ?? 0,
11411
- image: d.spec?.template?.spec?.containers?.[0]?.image
11412
- })),
11413
- services: servicesResp.items.map((s) => ({
11414
- name: s.metadata?.name,
11415
- type: s.spec?.type,
11416
- clusterIP: s.spec?.clusterIP
11417
- })),
11418
- configMaps: configMapsResp.items.map((cm) => cm.metadata?.name)
11419
- };
11420
- } catch (err) {
11421
- return { error: err.message ?? String(err) };
11422
- }
11423
- }
11424
- }),
11425
- // ── CURRENT USAGE ────────────────────────────────────────────────────
11426
- get_cluster_usage: (0, import_back.tool)({
11427
- description: "Returns current overall cluster resource usage: CPU%, memory%, network Mbps, total vCPUs and total memory GB.",
11428
- inputSchema: import_back.z.object({}),
11429
- execute: async () => {
11430
- context.trace("get_cluster_usage", {});
11431
- if (context.clusterMetrics.length === 0) return { error: "No metrics available yet" };
11432
- const latest = context.clusterMetrics[context.clusterMetrics.length - 1];
11433
- return {
11434
- vcpus: latest.cluster.vcpus,
11435
- memoryGB: Math.round(latest.cluster.memory / 1024 / 1024 / 1024 * 100) / 100,
11436
- cpuUsagePercent: Math.round(latest.cluster.cpuUsage * 100) / 100,
11437
- memoryUsagePercent: Math.round(latest.cluster.memoryUsage * 100) / 100,
11438
- networkTxMbps: Math.round(latest.cluster.txmbps * 100) / 100,
11439
- networkRxMbps: Math.round(latest.cluster.rxmbps * 100) / 100,
11440
- metricsIntervalSeconds: latest.metricsInterval
11441
- };
11442
- }
11443
- }),
11444
- get_node_usage: (0, import_back.tool)({
11445
- description: "Returns current CPU and memory usage for one node or all nodes from the latest metrics reading.",
11446
- inputSchema: import_back.z.object({
11447
- nodeName: import_back.z.string().optional().describe("Node name to filter (omit for all nodes)")
11448
- }),
11449
- execute: async ({ nodeName }) => {
11450
- context.trace("get_node_usage", { nodeName: nodeName ?? "*" });
11451
- if (context.clusterMetrics.length === 0) return { error: "No metrics available yet" };
11452
- const latest = context.clusterMetrics[context.clusterMetrics.length - 1];
11453
- const nodes = nodeName ? latest.nodes.filter((n) => n.name === nodeName) : latest.nodes;
11454
- return nodes.map((node) => ({
11455
- name: node.name,
11456
- cpuMillicores: Math.round((node.summary?.cpu?.usageNanoCores ?? 0) / 1e6),
11457
- memoryMB: Math.round((node.summary?.memory?.workingSetBytes ?? 0) / 1024 / 1024),
11458
- networkRxMB: Math.round((node.summary?.network?.rxBytes ?? 0) / 1024 / 1024),
11459
- networkTxMB: Math.round((node.summary?.network?.txBytes ?? 0) / 1024 / 1024),
11460
- podCount: node.summary?.pods?.length ?? 0,
11461
- timestamp: node.timestamp
11462
- }));
11463
- }
11464
- }),
11465
- get_deployment_usage: (0, import_back.tool)({
11466
- description: "Returns current aggregated CPU and memory usage for all pods belonging to a specific deployment.",
11467
- inputSchema: import_back.z.object({
11468
- namespace: import_back.z.string().describe("Namespace of the deployment"),
11469
- name: import_back.z.string().describe("Name of the deployment")
11470
- }),
11471
- execute: async ({ namespace, name }) => {
11472
- context.trace("get_deployment_usage", { namespace, name });
11473
- try {
11474
- if (context.clusterMetrics.length === 0) return { error: "No metrics available yet" };
11475
- const latest = context.clusterMetrics[context.clusterMetrics.length - 1];
11476
- const deployResp = await context.clusterInfo.appsApi.readNamespacedDeployment({ name, namespace });
11477
- const labelSelector = Object.entries(deployResp.spec?.selector?.matchLabels ?? {}).map(([k, v]) => `${k}=${v}`).join(",");
11478
- const podsResp = await context.clusterInfo.coreApi.listNamespacedPod({ namespace, labelSelector });
11479
- const podNames = new Set(podsResp.items.map((p) => p.metadata?.name));
11480
- let totalCpuNanoCores = 0;
11481
- let totalMemoryBytes = 0;
11482
- let podCount = 0;
11483
- for (const node of latest.nodes) {
11484
- for (const pod of node.summary?.pods ?? []) {
11485
- if (pod.podRef?.namespace === namespace && podNames.has(pod.podRef?.name)) {
11486
- totalCpuNanoCores += pod.cpu?.usageNanoCores ?? 0;
11487
- totalMemoryBytes += pod.memory?.workingSetBytes ?? 0;
11488
- podCount++;
11489
- }
11490
- }
11491
- }
11492
- return {
11493
- deployment: name,
11494
- namespace,
11495
- podCount,
11496
- cpuMillicores: Math.round(totalCpuNanoCores / 1e6),
11497
- memoryMB: Math.round(totalMemoryBytes / 1024 / 1024),
11498
- timestamp: latest.nodes[0]?.timestamp
11499
- };
11500
- } catch (err) {
11501
- return { error: err.message ?? String(err) };
11502
- }
11503
- }
11504
- }),
11505
- // ── HISTORICAL USAGE ─────────────────────────────────────────────────
11506
- get_prev_cluster_usage: (0, import_back.tool)({
11507
- description: "Returns historical overall cluster usage over the last N metrics readings (CPU%, memory%, network Mbps).",
11508
- inputSchema: import_back.z.object({
11509
- count: import_back.z.number().optional().describe("Number of historical readings to return (default: 5)")
11510
- }),
11511
- execute: async ({ count = 5 }) => {
11512
- context.trace("get_prev_cluster_usage", { count });
11513
- if (context.clusterMetrics.length === 0) return { error: "No metrics available yet" };
11514
- return context.clusterMetrics.slice(-count).map((reading) => ({
11515
- vcpus: reading.cluster.vcpus,
11516
- memoryGB: Math.round(reading.cluster.memory / 1024 / 1024 / 1024 * 100) / 100,
11517
- cpuUsagePercent: Math.round(reading.cluster.cpuUsage * 100) / 100,
11518
- memoryUsagePercent: Math.round(reading.cluster.memoryUsage * 100) / 100,
11519
- networkTxMbps: Math.round(reading.cluster.txmbps * 100) / 100,
11520
- networkRxMbps: Math.round(reading.cluster.rxmbps * 100) / 100
11521
- }));
11522
- }
11523
- }),
11524
- get_prev_node_usage: (0, import_back.tool)({
11525
- description: "Returns historical CPU and memory usage for one or all nodes over the last N metrics readings.",
11526
- inputSchema: import_back.z.object({
11527
- nodeName: import_back.z.string().optional().describe("Node name to filter (omit for all nodes)"),
11528
- count: import_back.z.number().optional().describe("Number of historical readings (default: 5)")
11529
- }),
11530
- execute: async ({ nodeName, count = 5 }) => {
11531
- context.trace("get_prev_node_usage", { nodeName: nodeName ?? "*", count });
11532
- if (context.clusterMetrics.length === 0) return { error: "No metrics available yet" };
11533
- return context.clusterMetrics.slice(-count).map((reading) => ({
11534
- nodes: (nodeName ? reading.nodes.filter((n) => n.name === nodeName) : reading.nodes).map((node) => ({
11535
- name: node.name,
11536
- cpuMillicores: Math.round((node.summary?.cpu?.usageNanoCores ?? 0) / 1e6),
11537
- memoryMB: Math.round((node.summary?.memory?.workingSetBytes ?? 0) / 1024 / 1024),
11538
- timestamp: node.timestamp
11539
- }))
11540
- }));
11541
- }
11542
- }),
11543
- get_prev_deployment_usage: (0, import_back.tool)({
11544
- description: "Returns historical aggregated CPU and memory usage for a deployment over the last N metrics readings.",
11545
- inputSchema: import_back.z.object({
11546
- namespace: import_back.z.string().describe("Namespace of the deployment"),
11547
- name: import_back.z.string().describe("Name of the deployment"),
11548
- count: import_back.z.number().optional().describe("Number of historical readings (default: 5)")
11549
- }),
11550
- execute: async ({ namespace, name, count = 5 }) => {
11551
- context.trace("get_prev_deployment_usage", { namespace, name, count });
11552
- try {
11553
- if (context.clusterMetrics.length === 0) return { error: "No metrics available yet" };
11554
- const deployResp = await context.clusterInfo.appsApi.readNamespacedDeployment({ name, namespace });
11555
- const labelSelector = Object.entries(deployResp.spec?.selector?.matchLabels ?? {}).map(([k, v]) => `${k}=${v}`).join(",");
11556
- const podsResp = await context.clusterInfo.coreApi.listNamespacedPod({ namespace, labelSelector });
11557
- const podNames = new Set(podsResp.items.map((p) => p.metadata?.name));
11558
- return context.clusterMetrics.slice(-count).map((reading) => {
11559
- let totalCpuNanoCores = 0;
11560
- let totalMemoryBytes = 0;
11561
- let podCount = 0;
11562
- for (const node of reading.nodes) {
11563
- for (const pod of node.summary?.pods ?? []) {
11564
- if (pod.podRef?.namespace === namespace && podNames.has(pod.podRef?.name)) {
11565
- totalCpuNanoCores += pod.cpu?.usageNanoCores ?? 0;
11566
- totalMemoryBytes += pod.memory?.workingSetBytes ?? 0;
11567
- podCount++;
11568
- }
11569
- }
11570
- }
11571
- return {
11572
- deployment: name,
11573
- namespace,
11574
- podCount,
11575
- cpuMillicores: Math.round(totalCpuNanoCores / 1e6),
11576
- memoryMB: Math.round(totalMemoryBytes / 1024 / 1024),
11577
- timestamp: reading.nodes[0]?.timestamp
11578
- };
11579
- });
11580
- } catch (err) {
11581
- return { error: err.message ?? String(err) };
11582
- }
11583
- }
11584
- }),
11585
- get_prev_space_data: (0, import_back.tool)({
11586
- description: "Returns historical aggregated CPU and memory usage for all pods in a namespace over the last N metrics readings.",
11587
- inputSchema: import_back.z.object({
11588
- namespace: import_back.z.string().describe("Namespace name"),
11589
- count: import_back.z.number().optional().describe("Number of historical readings (default: 5)")
11590
- }),
11591
- execute: async ({ namespace, count = 5 }) => {
11592
- context.trace("get_prev_space_data", { namespace, count });
11593
- if (context.clusterMetrics.length === 0) return { error: "No metrics available yet" };
11594
- return context.clusterMetrics.slice(-count).map((reading) => {
11595
- let totalCpuNanoCores = 0;
11596
- let totalMemoryBytes = 0;
11597
- let podCount = 0;
11598
- for (const node of reading.nodes) {
11599
- for (const pod of node.summary?.pods ?? []) {
11600
- if (pod.podRef?.namespace === namespace) {
11601
- totalCpuNanoCores += pod.cpu?.usageNanoCores ?? 0;
11602
- totalMemoryBytes += pod.memory?.workingSetBytes ?? 0;
11603
- podCount++;
11604
- }
11605
- }
11606
- }
11607
- return {
11608
- namespace,
11609
- podCount,
11610
- cpuMillicores: Math.round(totalCpuNanoCores / 1e6),
11611
- memoryMB: Math.round(totalMemoryBytes / 1024 / 1024),
11612
- timestamp: reading.nodes[0]?.timestamp
11613
- };
11614
- });
11615
- }
11616
- }),
11617
- // ── CLUSTER ACTIONS ──────────────────────────────────────────────────
11618
- add_node: (0, import_back.tool)({
11619
- description: "Adds a new agent node to the cluster. For k3d uses `k3d node create <suffix> --cluster <name> --role agent`. For cloud providers (AKS/EKS/GKE) not yet implemented.",
11620
- inputSchema: import_back.z.object({
11621
- nodeName: import_back.z.string().optional().describe("Suffix for the new node name. For k3d the Kubernetes node will be named k3d-<cluster>-<nodeName>-0. Auto-generated if omitted."),
11622
- nodePoolName: import_back.z.string().optional().describe("Node pool name (cloud provider specific, ignored for k3d)")
11623
- }),
11624
- execute: async ({ nodeName, nodePoolName }) => {
11625
- context.trace("add_node", { nodeName: nodeName ?? "auto", nodePoolName: nodePoolName ?? "default" });
11626
- if (context.clusterInfo.flavour === "k3d") {
11627
- const suffix = nodeName ?? `agent-${Date.now()}`;
11628
- const clusterName = context.clusterInfo.name.replace(/^k3d-/, "");
11629
- try {
11630
- const { stdout, stderr } = await execAsync(`k3d node create ${suffix} --cluster ${clusterName} --role agent`, { timeout: 12e4 });
11631
- return { success: true, message: `New agent node '${suffix}' added to cluster '${clusterName}'`, stdout, stderr };
11632
- } catch (err) {
11633
- return { success: false, error: err.message ?? String(err) };
11634
- }
11635
- }
11636
- return { success: false, message: `add_node not yet implemented for flavour '${context.clusterInfo.flavour}'` };
11637
- }
11638
- }),
11639
- remove_node: (0, import_back.tool)({
11640
- description: "Removes a node from the cluster. Cordons it first, then deletes it. For k3d uses `k3d node delete`. For cloud providers (AKS/EKS/GKE) not yet implemented.",
11641
- inputSchema: import_back.z.object({
11642
- nodeName: import_back.z.string().describe("Name of the Kubernetes node to remove (e.g. k3d-mycluster-agent-0)"),
11643
- nodePoolName: import_back.z.string().optional().describe("Node pool name (cloud provider specific, ignored for k3d)")
11644
- }),
11645
- execute: async ({ nodeName, nodePoolName }) => {
11646
- context.trace("remove_node", { nodeName, nodePoolName: nodePoolName ?? "default" });
11647
- try {
11648
- await context.clusterInfo.coreApi.patchNode({ name: nodeName, body: [{ op: "add", path: "/spec/unschedulable", value: true }] });
11649
- } catch (_2) {
11650
- }
11651
- if (context.clusterInfo.flavour === "k3d") {
11652
- try {
11653
- const { stdout, stderr } = await execAsync(`k3d node delete ${nodeName}`, { timeout: 6e4 });
11654
- return { success: true, message: `Node '${nodeName}' removed from cluster`, stdout, stderr };
11655
- } catch (err) {
11656
- return { success: false, error: err.message ?? String(err) };
11657
- }
11658
- }
11659
- return { success: false, message: `remove_node not yet implemented for flavour '${context.clusterInfo.flavour}'` };
11660
- }
11661
- }),
11662
- stop_node: (0, import_back.tool)({
11663
- description: "Stops a running cluster node: cordons it in Kubernetes (marks it unschedulable) then stops the underlying container. For k3d uses `k3d node stop`. For other flavours only the cordon is applied.",
11664
- inputSchema: import_back.z.object({
11665
- nodeName: import_back.z.string().describe("Name of the Kubernetes node to stop (e.g. k3d-mycluster-agent-0)")
11666
- }),
11667
- execute: async ({ nodeName }) => {
11668
- context.trace("stop_node", { nodeName });
11669
- try {
11670
- await context.clusterInfo.coreApi.patchNode({ name: nodeName, body: [{ op: "add", path: "/spec/unschedulable", value: true }] });
11671
- } catch (err) {
11672
- return { success: false, error: `Failed to cordon node: ${err.message ?? String(err)}` };
11673
- }
11674
- if (context.clusterInfo.flavour !== "k3d") {
11675
- return { success: false, message: `Node '${nodeName}' cordoned but container stop is only implemented for k3d (flavour is '${context.clusterInfo.flavour}')` };
11676
- }
11677
- try {
11678
- const { stdout, stderr } = await execAsync(`k3d node stop ${nodeName}`, { timeout: 3e4 });
11679
- return { success: true, message: `Node '${nodeName}' cordoned and stopped`, stdout, stderr };
11680
- } catch (err) {
11681
- return { success: false, error: `Node cordoned but container stop failed: ${err.message ?? String(err)}` };
11682
- }
11683
- }
11684
- }),
11685
- start_node: (0, import_back.tool)({
11686
- description: "Starts a previously stopped cluster node and uncordons it. For k3d uses `k3d node start`. For other flavours only the uncordon is applied.",
11687
- inputSchema: import_back.z.object({
11688
- nodeName: import_back.z.string().describe("Name of the Kubernetes node to start (e.g. k3d-mycluster-agent-0)")
11689
- }),
11690
- execute: async ({ nodeName }) => {
11691
- context.trace("start_node", { nodeName });
11692
- if (context.clusterInfo.flavour === "k3d") {
11693
- try {
11694
- const { stdout, stderr } = await execAsync(`k3d node start ${nodeName}`, { timeout: 3e4 });
11695
- try {
11696
- await context.clusterInfo.coreApi.patchNode({ name: nodeName, body: [{ op: "add", path: "/spec/unschedulable", value: false }] });
11697
- } catch (_2) {
11698
- }
11699
- return { success: true, message: `Node '${nodeName}' started and uncordoned`, stdout, stderr };
11700
- } catch (err) {
11701
- return { success: false, error: err.message ?? String(err) };
11702
- }
11703
- }
11704
- try {
11705
- await context.clusterInfo.coreApi.patchNode({ name: nodeName, body: [{ op: "add", path: "/spec/unschedulable", value: false }] });
11706
- return { success: false, message: `Node '${nodeName}' uncordoned but container start is only implemented for k3d (flavour is '${context.clusterInfo.flavour}')` };
11707
- } catch (err) {
11708
- return { success: false, error: `Container start not implemented for this flavour and uncordon failed: ${err.message ?? String(err)}` };
11709
- }
11710
- }
11711
- }),
11712
- add_replica: (0, import_back.tool)({
11713
- description: "Scales up a deployment by adding one replica.",
11714
- inputSchema: import_back.z.object({
11715
- namespace: import_back.z.string().describe("Namespace of the deployment"),
11716
- name: import_back.z.string().describe("Name of the deployment")
11717
- }),
11718
- execute: async ({ namespace, name }) => {
11719
- context.trace("add_replica", { namespace, name });
11720
- try {
11721
- const deployResp = await context.clusterInfo.appsApi.readNamespacedDeployment({ name, namespace });
11722
- const currentReplicas = deployResp.spec?.replicas ?? 1;
11723
- const newReplicas = currentReplicas + 1;
11724
- const patch = [{ op: "replace", path: "/spec/replicas", value: newReplicas }];
11725
- await context.clusterInfo.appsApi.patchNamespacedDeployment({ name, namespace, body: patch });
11726
- return { success: true, message: `Deployment ${namespace}/${name} scaled from ${currentReplicas} to ${newReplicas} replicas` };
11727
- } catch (err) {
11728
- return { success: false, error: err.message ?? String(err) };
11729
- }
11730
- }
11731
- }),
11732
- remove_replica: (0, import_back.tool)({
11733
- description: "Scales down a deployment by removing one replica. Minimum of 1 replica is enforced.",
11734
- inputSchema: import_back.z.object({
11735
- namespace: import_back.z.string().describe("Namespace of the deployment"),
11736
- name: import_back.z.string().describe("Name of the deployment")
11737
- }),
11738
- execute: async ({ namespace, name }) => {
11739
- context.trace("remove_replica", { namespace, name });
11740
- try {
11741
- const deployResp = await context.clusterInfo.appsApi.readNamespacedDeployment({ name, namespace });
11742
- const currentReplicas = deployResp.spec?.replicas ?? 1;
11743
- if (currentReplicas <= 1) return { success: false, message: `Deployment ${namespace}/${name} already at minimum (${currentReplicas} replica)` };
11744
- const newReplicas = currentReplicas - 1;
11745
- const patch = [{ op: "replace", path: "/spec/replicas", value: newReplicas }];
11746
- await context.clusterInfo.appsApi.patchNamespacedDeployment({ name, namespace, body: patch });
11747
- return { success: true, message: `Deployment ${namespace}/${name} scaled from ${currentReplicas} to ${newReplicas} replicas` };
11748
- } catch (err) {
11749
- return { success: false, error: err.message ?? String(err) };
11750
- }
11751
- }
11752
- }),
11753
- // ── MISC ─────────────────────────────────────────────────────────────
11754
- times_two: (0, import_back.tool)({
11755
- description: "Multiplies a number by two",
11756
- inputSchema: import_back.z.object({
11757
- data: import_back.z.number()
11758
- }),
11759
- execute: async ({ data }) => {
11760
- context.trace("times_two", { data });
11761
- return data * 2;
11762
- }
11763
- }),
11764
- father_of: (0, import_back.tool)({
11765
- description: "Returns the name of the father of a person",
11766
- inputSchema: import_back.z.object({
11767
- data: import_back.z.string().describe("The name of the person whose father you want to discover")
11768
- }),
11769
- execute: async ({ data }) => {
11770
- context.trace("father_of", { data });
11771
- return "Julio";
11772
- }
11773
- })
11774
- };
11775
- };
11776
- var getToolByName = (name, context) => {
11777
- const tools = createTools(context);
11778
- return tools[name];
11779
- };
11780
- var toolInfoList = [
11781
- { name: "get_node_data", description: "Returns configuration info about all Kubernetes nodes (name, IP). Configuration only \u2014 not workload or usage data." },
11782
- { name: "get_cluster_data", description: "Returns general cluster info: name, flavour (AKS/EKS/GKE/k3s/k3d), total vCPUs, total memory, node count and readiness status." },
11783
- { name: "get_workload_data", description: "Returns all workloads in the cluster: deployments, statefulsets, daemonsets, pods and services. Optionally filter by namespace." },
11784
- { name: "get_space_data", description: "Returns all resources in a specific Kubernetes namespace: pods (with restart count), deployments, services, configmap names." },
11785
- { name: "get_cluster_usage", description: "Returns current overall cluster resource usage: CPU%, memory%, network Mbps, total vCPUs and total memory GB." },
11786
- { name: "get_node_usage", description: "Returns current CPU and memory usage for one node or all nodes from the latest metrics reading." },
11787
- { name: "get_deployment_usage", description: "Returns current aggregated CPU and memory usage for all pods belonging to a specific deployment." },
11788
- { name: "get_prev_cluster_usage", description: "Returns historical overall cluster usage over the last N metrics readings (CPU%, memory%, network Mbps)." },
11789
- { name: "get_prev_node_usage", description: "Returns historical CPU and memory usage for one or all nodes over the last N metrics readings." },
11790
- { name: "get_prev_deployment_usage", description: "Returns historical aggregated CPU and memory usage for a deployment over the last N metrics readings." },
11791
- { name: "get_prev_space_data", description: "Returns historical aggregated CPU and memory usage for all pods in a namespace over the last N metrics readings." },
11792
- { name: "add_node", description: "Adds a new agent node to the cluster. For k3d uses `k3d node create`. Cloud providers not yet implemented." },
11793
- { name: "remove_node", description: "Removes a node from the cluster (cordon + delete). For k3d uses `k3d node delete`. Cloud providers not yet implemented." },
11794
- { name: "stop_node", description: "Stops a running node: cordons it and stops the container. For k3d uses `k3d node stop`." },
11795
- { name: "start_node", description: "Starts a stopped node and uncordons it. For k3d uses `k3d node start`." },
11796
- { name: "add_replica", description: "Scales up a deployment by adding one replica." },
11797
- { name: "remove_replica", description: "Scales down a deployment by removing one replica. Minimum of 1 replica is enforced." },
11798
- { name: "times_two", description: "Multiplies a number by two." },
11799
- { name: "father_of", description: "Returns the name of the father of a person." }
11800
- ];
11801
-
11802
- // src/back/index.ts
11266
+ var import_back2 = __toESM(require_back(), 1);
11803
11267
  var _ = require_lodash();
11804
11268
  var nunjucks = require_nunjucks();
11805
11269
  nunjucks.configure({ autoescape: true });
@@ -11818,8 +11282,12 @@ var PinocchioChannel = class {
11818
11282
  triggers: [],
11819
11283
  llms: []
11820
11284
  };
11285
+ this.startChannelReady = null;
11821
11286
  this.playgroundTrigger = void 0;
11822
- this.startChannel = async () => {
11287
+ this.startChannel = () => {
11288
+ this.startChannelReady = this._startChannelImpl();
11289
+ };
11290
+ this._startChannelImpl = async () => {
11823
11291
  this.clusterInfo.addSubscriber("metrics", this, {});
11824
11292
  this.clusterInfo.addSubscriber("business", this, {
11825
11293
  spaces: [
@@ -11835,14 +11303,23 @@ var PinocchioChannel = class {
11835
11303
  });
11836
11304
  let provs = await this.backChannelObject.readStorageCommon(import_kwirth_common_ai.STORAGE_KEY_PROVIDERS, true);
11837
11305
  if (provs) this.providers = provs;
11838
- let config = await this.backChannelObject.readStorage("pinocchio-config", false);
11306
+ let rawConfig = await this.backChannelObject.readStorage("pinocchio-config", false);
11307
+ let config = null;
11308
+ if (typeof rawConfig === "string") {
11309
+ try {
11310
+ config = JSON.parse(rawConfig);
11311
+ } catch {
11312
+ }
11313
+ } else if (rawConfig) {
11314
+ config = rawConfig;
11315
+ }
11839
11316
  if (config) this.pinocchioConfig = config;
11840
11317
  try {
11841
11318
  const sharedLlms = await this.backChannelObject.readStorageCommon(import_kwirth_common_ai.STORAGE_KEY_LLMS, false);
11842
11319
  if (sharedLlms?.length) this.pinocchioConfig.llms = sharedLlms;
11843
11320
  } catch {
11844
11321
  }
11845
- (0, import_back2.loadModels)(this.providers, this.backChannelObject);
11322
+ (0, import_back.loadModels)(this.providers, this.backChannelObject);
11846
11323
  };
11847
11324
  this.getChannelData = () => {
11848
11325
  return {
@@ -11869,7 +11346,7 @@ var PinocchioChannel = class {
11869
11346
  this.broadcastError(`Cannot find LLM with id '${version.llm}'`);
11870
11347
  return void 0;
11871
11348
  }
11872
- const model = (0, import_back2.buildModel)(llm, this.providers);
11349
+ const model = (0, import_back.buildModel)(llm, this.providers);
11873
11350
  if (!model) {
11874
11351
  this.broadcastError(`Cannot build model for LLM '${version.llm}' (provider: ${llm.provider})`);
11875
11352
  return void 0;
@@ -11910,17 +11387,15 @@ var PinocchioChannel = class {
11910
11387
  let temperature = llm.temperature;
11911
11388
  if (temperature < 0) temperature = 0;
11912
11389
  if (temperature > 1) temperature = 1;
11913
- let context = {
11390
+ const toolContext = {
11914
11391
  origin: "Pinocchio",
11915
11392
  nodes: await this.clusterInfo.getNodes(),
11916
11393
  clusterInfo: this.clusterInfo,
11917
11394
  clusterMetrics: this.clusterMetrics,
11918
11395
  trace: (toolName, args) => this.backChannelObject.logTrace?.(`[pinocchio] tool ${toolName} ${JSON.stringify(args)}`)
11919
11396
  };
11920
- let tools = {};
11921
- for (let toolName of version.tools) {
11922
- tools[toolName] = getToolByName(toolName, context);
11923
- }
11397
+ const toolNames = version.autoTools ? import_back.toolInfoList.map((t) => t.name) : version.tools ?? [];
11398
+ const tools = Object.fromEntries(toolNames.filter((n) => n in import_back.tools).map((n) => [n, import_back.tools[n]]));
11924
11399
  let providerOptions = {};
11925
11400
  let errorPath = "";
11926
11401
  switch (llm.provider) {
@@ -11945,6 +11420,7 @@ var PinocchioChannel = class {
11945
11420
  errorPath,
11946
11421
  temperature,
11947
11422
  tools,
11423
+ toolContext,
11948
11424
  prompt,
11949
11425
  system
11950
11426
  };
@@ -11987,7 +11463,7 @@ var PinocchioChannel = class {
11987
11463
  flow: import_kwirth_common.EInstanceMessageFlow.RESPONSE,
11988
11464
  type: import_kwirth_common.EInstanceMessageType.DATA,
11989
11465
  instance: instance.instanceId,
11990
- toolsAvailable: toolInfoList
11466
+ toolsAvailable: import_back.toolInfoList
11991
11467
  };
11992
11468
  webSocket.send(JSON.stringify(msgToolsAvailable));
11993
11469
  break;
@@ -12009,7 +11485,7 @@ var PinocchioChannel = class {
12009
11485
  let provs = pinocchioMessage.data;
12010
11486
  this.providers = provs;
12011
11487
  await this.backChannelObject.writeStorageCommon(import_kwirth_common_ai.STORAGE_KEY_PROVIDERS, true, provs);
12012
- await (0, import_back2.loadModels)(this.providers, this.backChannelObject);
11488
+ await (0, import_back.loadModels)(this.providers, this.backChannelObject);
12013
11489
  this.executeProvidersGet();
12014
11490
  this.sendSignalMessage(webSocket, import_kwirth_common.EInstanceMessageAction.COMMAND, import_kwirth_common.EInstanceMessageFlow.RESPONSE, import_kwirth_common.ESignalMessageLevel.INFO, instance.instanceId, "Providers updated");
12015
11491
  break;
@@ -12049,7 +11525,7 @@ var PinocchioChannel = class {
12049
11525
  try {
12050
11526
  const invocation = await this.buildModelInvocation(dummyTrigger, version, fakeEvent);
12051
11527
  if (!invocation) return;
12052
- const { model, temperature, providerOptions, tools, prompt: effectivePrompt } = invocation;
11528
+ const { model, temperature, providerOptions, tools, toolContext, prompt: effectivePrompt } = invocation;
12053
11529
  this.broadcastPlaygroundMessage(`[Playground] type: ${triggerType}`);
12054
11530
  this.broadcastPlaygroundMessage(`[Playground] llm: ${version.llm}`);
12055
11531
  this.broadcastPlaygroundMessage(`[Playground] system: ${version.system || "(none)"}`);
@@ -12058,7 +11534,7 @@ var PinocchioChannel = class {
12058
11534
  let activeTools = tools;
12059
11535
  if (version.autoTools && Object.keys(tools).length > 0) {
12060
11536
  const toolListStr = Object.keys(tools).join(", ");
12061
- const { text: selectionText } = await (0, import_back3.generateText)({
11537
+ const { text: selectionText } = await (0, import_back2.generateText)({
12062
11538
  model,
12063
11539
  temperature,
12064
11540
  providerOptions,
@@ -12071,15 +11547,15 @@ Available tools: ${toolListStr}`
12071
11547
  this.broadcastPlaygroundMessage(`[Auto tools] selected: ${selectedNames.join(", ") || "(none)"}`);
12072
11548
  activeTools = Object.fromEntries(selectedNames.map((n) => [n, tools[n]]));
12073
11549
  }
12074
- const { text: phase1Text, usage: usage1, steps } = await (0, import_back3.generateText)({
11550
+ const { text: phase1Text, usage: usage1, steps } = await (0, import_back.runWithToolContext)(toolContext, () => (0, import_back2.generateText)({
12075
11551
  model,
12076
11552
  temperature,
12077
- stopWhen: (0, import_back3.stepCountIs)(version.steps || 5),
11553
+ stopWhen: (0, import_back2.stepCountIs)(version.steps || 5),
12078
11554
  tools: activeTools,
12079
11555
  providerOptions,
12080
11556
  system: version.system || "You are a helpful assistant.",
12081
11557
  prompt: effectivePrompt
12082
- });
11558
+ }));
12083
11559
  const toolLines = [];
12084
11560
  for (const step of steps) {
12085
11561
  const s = step;
@@ -12103,7 +11579,7 @@ Information gathered from tools:
12103
11579
  ${toolLines.join("\n")}
12104
11580
 
12105
11581
  Please provide a comprehensive answer based on the above data.`;
12106
- const { text: phase2Text, usage: usage2 } = await (0, import_back3.generateText)({
11582
+ const { text: phase2Text, usage: usage2 } = await (0, import_back2.generateText)({
12107
11583
  model,
12108
11584
  temperature,
12109
11585
  providerOptions,
@@ -12213,6 +11689,7 @@ Please provide a comprehensive answer based on the above data.`;
12213
11689
  // PRIVATE
12214
11690
  // *************************************************************************************
12215
11691
  this.executeConfigGet = async () => {
11692
+ if (this.startChannelReady) await this.startChannelReady;
12216
11693
  try {
12217
11694
  const sharedLlms = await this.backChannelObject.readStorageCommon(import_kwirth_common_ai.STORAGE_KEY_LLMS, false);
12218
11695
  if (sharedLlms?.length) this.pinocchioConfig.llms = sharedLlms;
@@ -12239,7 +11716,7 @@ Please provide a comprehensive answer based on the above data.`;
12239
11716
  const freshProviders = await this.backChannelObject.readStorageCommon(import_kwirth_common_ai.STORAGE_KEY_PROVIDERS, true);
12240
11717
  if (freshProviders?.length) {
12241
11718
  this.providers = freshProviders;
12242
- await (0, import_back2.loadModels)(this.providers, this.backChannelObject);
11719
+ await (0, import_back.loadModels)(this.providers, this.backChannelObject);
12243
11720
  }
12244
11721
  } catch {
12245
11722
  }
@@ -12373,23 +11850,23 @@ Please provide a comprehensive answer based on the above data.`;
12373
11850
  for (let t of this.pinocchioConfig.triggers.filter((t2) => t2.trigger === "business")) {
12374
11851
  for (let version of t.versions.filter((v) => v.enabled)) {
12375
11852
  try {
12376
- let { llmModelId, llmProviderId, model, temperature, providerOptions, errorPath, system, prompt, tools } = await this.buildModelInvocation(t, version, businessEvent) || {};
11853
+ let { llmModelId, llmProviderId, model, temperature, providerOptions, errorPath, system, prompt, tools, toolContext } = await this.buildModelInvocation(t, version, businessEvent) || {};
12377
11854
  if (!model) return;
12378
11855
  this.broadcastMessage(`Received business event ${JSON.stringify(businessEvent.last.event)}`);
12379
- const { output, usage, steps } = await (0, import_back3.generateText)({
11856
+ const { output, usage, steps } = await (0, import_back.runWithToolContext)(toolContext, () => (0, import_back2.generateText)({
12380
11857
  model,
12381
11858
  temperature,
12382
- stopWhen: (0, import_back3.stepCountIs)(15),
11859
+ stopWhen: (0, import_back2.stepCountIs)(15),
12383
11860
  tools,
12384
11861
  providerOptions,
12385
- output: import_back3.Output.object({
12386
- schema: import_back3.z.object({
12387
- response: import_back3.z.string().describe("response to the question")
11862
+ output: import_back2.Output.object({
11863
+ schema: import_back2.z.object({
11864
+ response: import_back2.z.string().describe("response to the question")
12388
11865
  })
12389
11866
  }),
12390
11867
  system: "Use the tools provided to find information, and once you have the data, format your final response strictly as a JSON object according to the schema.",
12391
11868
  prompt: prompt || "Hi AI, how are you?"
12392
- });
11869
+ }));
12393
11870
  this.broadcastMessage(JSON.stringify(output.response));
12394
11871
  } catch (err) {
12395
11872
  let message = `Pinocchio analysis ended in error when processing 'business' while analyzing`;
@@ -12419,29 +11896,29 @@ Please provide a comprehensive answer based on the above data.`;
12419
11896
  continue;
12420
11897
  }
12421
11898
  }
12422
- let { llmModelId, llmProviderId, model, temperature, providerOptions, errorPath, system, prompt, tools } = await this.buildModelInvocation(t, version, eventsEvent) || {};
11899
+ let { llmModelId, llmProviderId, model, temperature, providerOptions, errorPath, system, prompt, tools, toolContext } = await this.buildModelInvocation(t, version, eventsEvent) || {};
12423
11900
  if (!model) return;
12424
11901
  try {
12425
- const { output, usage } = await (0, import_back3.generateText)({
11902
+ const { output, usage } = await (0, import_back.runWithToolContext)(toolContext, () => (0, import_back2.generateText)({
12426
11903
  model,
12427
11904
  temperature,
12428
11905
  tools,
12429
11906
  providerOptions,
12430
- output: import_back3.Output.object({
12431
- schema: import_back3.z.object({
12432
- findings: import_back3.z.array(
12433
- import_back3.z.object({
12434
- description: import_back3.z.string().min(1),
12435
- level: import_back3.z.enum(["low", "medium", "high", "critical"])
11907
+ output: import_back2.Output.object({
11908
+ schema: import_back2.z.object({
11909
+ findings: import_back2.z.array(
11910
+ import_back2.z.object({
11911
+ description: import_back2.z.string().min(1),
11912
+ level: import_back2.z.enum(["low", "medium", "high", "critical"])
12436
11913
  })
12437
11914
  ),
12438
- report: import_back3.z.string().min(1),
12439
- hardened_yaml: import_back3.z.string().min(1)
11915
+ report: import_back2.z.string().min(1),
11916
+ hardened_yaml: import_back2.z.string().min(1)
12440
11917
  })
12441
11918
  }),
12442
11919
  system: system || "You are a very polite AI system",
12443
11920
  prompt: prompt || "Hi AI, how are you?"
12444
- });
11921
+ }));
12445
11922
  let analysis = {
12446
11923
  text: `${eventsEvent.type} ${eventsEvent.obj.kind} '${eventsEvent.obj.metadata.name}' in namespace '${eventsEvent.obj.metadata.namespace}' [LLM:${llmProviderId}/${llmModelId}, IN:${usage.inputTokens}, OUT:${usage.outputTokens}]`,
12447
11924
  findings: output.findings,