From 624f4bfda496c27023eea7a142408daab4c49105 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Tue, 16 Dec 2025 13:48:18 +0100 Subject: [PATCH 1/4] Add `get_hosts` tool --- .../src/lib/infra/host.ts | 24 ++- .../onechat-common/tools/tool_result.ts | 7 +- .../onechat/onechat-server/allow_lists.ts | 1 + .../observability/plugins/infra/kibana.jsonc | 1 + .../agent_builder/register_data_providers.ts | 131 ++++++++++++ .../lib/adapters/framework/adapter_types.ts | 2 + .../plugins/infra/server/plugin.ts | 30 +-- .../get_infra_request_handler_context.ts | 44 +++++ .../observability/plugins/infra/tsconfig.json | 3 +- .../data_registry/data_registry_types.ts | 46 +++++ .../server/tools/get_hosts/README.md | 69 +++++++ .../server/tools/get_hosts/get_hosts.ts | 171 ++++++++++++++++ .../server/tools/index.ts | 1 + .../server/tools/register_tools.ts | 3 + .../apis/observability_agent_builder/index.ts | 1 + .../tools/get_hosts.spec.ts | 186 ++++++++++++++++++ .../create_synthetic_infra_data.ts | 119 +++++++++++ .../utils/synthtrace_scenarios/index.ts | 1 + 18 files changed, 804 insertions(+), 36 deletions(-) create mode 100644 x-pack/solutions/observability/plugins/infra/server/agent_builder/register_data_providers.ts create mode 100644 x-pack/solutions/observability/plugins/infra/server/utils/get_infra_request_handler_context.ts create mode 100644 x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/get_hosts/README.md create mode 100644 x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/get_hosts/get_hosts.ts create mode 100644 x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/observability_agent_builder/tools/get_hosts.spec.ts create mode 100644 x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/observability_agent_builder/utils/synthtrace_scenarios/create_synthetic_infra_data.ts diff --git a/src/platform/packages/shared/kbn-synthtrace-client/src/lib/infra/host.ts b/src/platform/packages/shared/kbn-synthtrace-client/src/lib/infra/host.ts index c7e975a827c50..6a6d147f3c473 100644 --- a/src/platform/packages/shared/kbn-synthtrace-client/src/lib/infra/host.ts +++ b/src/platform/packages/shared/kbn-synthtrace-client/src/lib/infra/host.ts @@ -50,16 +50,24 @@ export class Host extends Entity { }); } - memory() { + memory(fields?: { + 'system.memory.actual.free'?: number; + 'system.memory.actual.used.bytes'?: number; + 'system.memory.actual.used.pct'?: number; + 'system.memory.total'?: number; + 'system.memory.used.bytes'?: number; + 'system.memory.used.pct'?: number; + 'process.memory.pct'?: number; + }) { return new HostMetrics({ ...this.fields, - 'system.memory.actual.free': 44704067584, - 'system.memory.actual.used.bytes': 24015409152, - 'system.memory.actual.used.pct': 0.35, - 'system.memory.total': 68719476736, - 'system.memory.used.bytes': 39964708864, - 'system.memory.used.pct': 0.582, - 'process.memory.pct': 0.1, + 'system.memory.actual.free': fields?.['system.memory.actual.free'] ?? 44704067584, + 'system.memory.actual.used.bytes': fields?.['system.memory.actual.used.bytes'] ?? 24015409152, + 'system.memory.actual.used.pct': fields?.['system.memory.actual.used.pct'] ?? 0.35, + 'system.memory.total': fields?.['system.memory.total'] ?? 68719476736, + 'system.memory.used.bytes': fields?.['system.memory.used.bytes'] ?? 39964708864, + 'system.memory.used.pct': fields?.['system.memory.used.pct'] ?? 0.582, + 'process.memory.pct': fields?.['process.memory.pct'] ?? 0.1, 'metricset.period': 10000, 'metricset.name': 'memory', }); diff --git a/x-pack/platform/packages/shared/onechat/onechat-common/tools/tool_result.ts b/x-pack/platform/packages/shared/onechat/onechat-common/tools/tool_result.ts index 65a6200d83c05..89242bb54efdb 100644 --- a/x-pack/platform/packages/shared/onechat/onechat-common/tools/tool_result.ts +++ b/x-pack/platform/packages/shared/onechat/onechat-common/tools/tool_result.ts @@ -75,7 +75,10 @@ export interface VisualizationResult { }; } -export type OtherResult = ToolResultMixin>; +export type OtherResult> = ToolResultMixin< + ToolResultType.other, + T +>; export type ErrorResult = ToolResultMixin< ToolResultType.error, @@ -92,7 +95,7 @@ export type ToolResult = | QueryResult | VisualizationResult | DashboardResult - | OtherResult + | OtherResult | ErrorResult; export const isResourceResult = (result: ToolResult): result is ResourceResult => { diff --git a/x-pack/platform/packages/shared/onechat/onechat-server/allow_lists.ts b/x-pack/platform/packages/shared/onechat/onechat-server/allow_lists.ts index f715eed9e67eb..4c36aa01ae7ce 100644 --- a/x-pack/platform/packages/shared/onechat/onechat-server/allow_lists.ts +++ b/x-pack/platform/packages/shared/onechat/onechat-server/allow_lists.ts @@ -25,6 +25,7 @@ export const AGENT_BUILDER_BUILTIN_TOOLS: string[] = [ `${internalNamespaces.observability}.get_services`, `${internalNamespaces.observability}.get_downstream_dependencies`, `${internalNamespaces.observability}.get_correlated_logs`, + `${internalNamespaces.observability}.get_hosts`, // Dashboards 'platform.dashboard.create_dashboard', diff --git a/x-pack/solutions/observability/plugins/infra/kibana.jsonc b/x-pack/solutions/observability/plugins/infra/kibana.jsonc index aaa48ef9bc3b3..e9e25aad5a440 100644 --- a/x-pack/solutions/observability/plugins/infra/kibana.jsonc +++ b/x-pack/solutions/observability/plugins/infra/kibana.jsonc @@ -50,6 +50,7 @@ "cloud", "profilingDataAccess", "observabilityAIAssistant", + "observabilityAgentBuilder", "licenseManagement", "serverless" ], diff --git a/x-pack/solutions/observability/plugins/infra/server/agent_builder/register_data_providers.ts b/x-pack/solutions/observability/plugins/infra/server/agent_builder/register_data_providers.ts new file mode 100644 index 0000000000000..b2cbebb5c59fc --- /dev/null +++ b/x-pack/solutions/observability/plugins/infra/server/agent_builder/register_data_providers.ts @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { CoreSetup, Logger, KibanaRequest, CoreRequestHandlerContext } from '@kbn/core/server'; +import type { + InfraServerPluginSetupDeps, + InfraServerPluginStartDeps, +} from '../lib/adapters/framework'; +import { getHosts } from '../routes/infra/lib/host/get_hosts'; +import type { InfraBackendLibs } from '../lib/infra_types'; +import { getInfraMetricsClient } from '../lib/helpers/get_infra_metrics_client'; +import { getInfraAlertsClient } from '../lib/helpers/get_infra_alerts_client'; +import { getInfraRequestHandlerContext } from '../utils/get_infra_request_handler_context'; +import type { InfraPluginRequestHandlerContext } from '../types'; +import { getApmDataAccessClient } from '../lib/helpers/get_apm_data_access_client'; + +// Default metrics to retrieve - same as HOST_TABLE_METRICS in infra plugin +const DEFAULT_HOST_METRICS = [ + 'cpuV2', + 'memory', + 'memoryFree', + 'diskSpaceUsage', + 'normalizedLoad1m', + 'rxV2', + 'txV2', +] as const; + +export function registerDataProviders({ + core, + plugins, + libs, + logger, +}: { + core: CoreSetup; + plugins: InfraServerPluginSetupDeps; + libs: InfraBackendLibs; + logger: Logger; +}) { + const { observabilityAgentBuilder } = plugins; + if (!observabilityAgentBuilder) { + return; + } + + observabilityAgentBuilder.registerDataProvider( + 'infraHosts', + async ({ request, from, to, limit, kqlFilter, hostNames }) => { + const infraToolResources = await buildInfraToolResources({ + core, + plugins, + libs, + request, + }); + + // Build query filter + const mustFilters: unknown[] = []; + + if (kqlFilter) { + mustFilters.push({ + query_string: { + query: kqlFilter, + analyze_wildcard: true, + }, + }); + } + + if (hostNames && hostNames.length > 0) { + mustFilters.push({ + terms: { + 'host.name': hostNames, + }, + }); + } + + const query = mustFilters.length > 0 ? { bool: { must: mustFilters } } : undefined; + + const result = await getHosts({ + from: new Date(from).getTime(), + to: new Date(to).getTime(), + metrics: [...DEFAULT_HOST_METRICS], + limit, + query, + alertsClient: infraToolResources.alertsClient, + infraMetricsClient: infraToolResources.infraMetricsClient, + apmDataAccessServices: infraToolResources.apmDataAccessServices, + schema: 'ecs', + }); + + return result; + } + ); +} + +async function buildInfraToolResources({ + core, + plugins, + libs, + request, +}: { + core: CoreSetup; + plugins: InfraServerPluginSetupDeps; + libs: InfraBackendLibs; + request: KibanaRequest; +}) { + const [coreStart] = await core.getStartServices(); + const soClient = coreStart.savedObjects.getScopedClient(request, { includedHiddenTypes: [] }); + const uiSettingsClient = coreStart.uiSettings.asScopedToClient(soClient); + const esClient = coreStart.elasticsearch.client.asScoped(request); + + const coreContext = { + savedObjects: { client: soClient }, + uiSettings: { client: uiSettingsClient }, + elasticsearch: { client: esClient }, + } as unknown as CoreRequestHandlerContext; + + const infraContext = await getInfraRequestHandlerContext({ coreContext, request, plugins }); + const context = { + core: Promise.resolve(coreContext), + infra: Promise.resolve(infraContext), + } as unknown as InfraPluginRequestHandlerContext; + + const infraMetricsClient = await getInfraMetricsClient({ libs, context, request }); + const alertsClient = await getInfraAlertsClient({ libs, request }); + const apmDataAccessClient = getApmDataAccessClient({ request, libs, context }); + const apmDataAccessServices = await apmDataAccessClient.getServices(); + + return { infraMetricsClient, alertsClient, apmDataAccessServices }; +} diff --git a/x-pack/solutions/observability/plugins/infra/server/lib/adapters/framework/adapter_types.ts b/x-pack/solutions/observability/plugins/infra/server/lib/adapters/framework/adapter_types.ts index 1397c184ea729..201fd44e2ae0a 100644 --- a/x-pack/solutions/observability/plugins/infra/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/solutions/observability/plugins/infra/server/lib/adapters/framework/adapter_types.ts @@ -40,6 +40,7 @@ import type { } from '@kbn/apm-data-access-plugin/server'; import type { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/server'; import type { ServerlessPluginStart } from '@kbn/serverless/server'; +import type { ObservabilityAgentBuilderPluginSetup } from '@kbn/observability-agent-builder-plugin/server'; export interface InfraServerPluginSetupDeps { alerting: AlertingServerSetup; @@ -58,6 +59,7 @@ export interface InfraServerPluginSetupDeps { profilingDataAccess?: ProfilingDataAccessPluginSetup; apmDataAccess: ApmDataAccessPluginSetup; serverless?: ServerlessPluginStart; + observabilityAgentBuilder?: ObservabilityAgentBuilderPluginSetup; } export interface InfraServerPluginStartDeps { diff --git a/x-pack/solutions/observability/plugins/infra/server/plugin.ts b/x-pack/solutions/observability/plugins/infra/server/plugin.ts index 959d73b96a382..95b0945517335 100644 --- a/x-pack/solutions/observability/plugins/infra/server/plugin.ts +++ b/x-pack/solutions/observability/plugins/infra/server/plugin.ts @@ -16,7 +16,6 @@ import { InventoryLocatorDefinition, MetricsExplorerLocatorDefinition, } from '@kbn/observability-shared-plugin/common'; -import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; import { mapValues } from 'lodash'; import { LOGS_FEATURE_ID, METRICS_FEATURE_ID } from '../common/constants'; import { getMetricsFeature } from './features'; @@ -54,6 +53,8 @@ import type { } from './types'; import { UsageCollector } from './usage/usage_collector'; import { mapSourceToLogView } from './utils/map_source_to_log_view'; +import { registerDataProviders } from './agent_builder/register_data_providers'; +import { getInfraRequestHandlerContext } from './utils/get_infra_request_handler_context'; export interface KbnServer extends Server { usage: any; @@ -200,36 +201,15 @@ export class InfraServerPlugin 'infra', async (context, request) => { const coreContext = await context.core; - const savedObjectsClient = coreContext.savedObjects.client; - const uiSettingsClient = coreContext.uiSettings.client; - - const mlSystem = plugins.ml?.mlSystemProvider(request, savedObjectsClient); - const mlAnomalyDetectors = plugins.ml?.anomalyDetectorsProvider( - request, - savedObjectsClient - ); - const spaceId = plugins.spaces?.spacesService.getSpaceId(request) ?? DEFAULT_SPACE_ID; - - const getMetricsIndices = async () => { - return metricsClient.getMetricIndices({ - savedObjectsClient, - }); - }; - - return { - mlAnomalyDetectors, - mlSystem, - spaceId, - savedObjectsClient, - uiSettingsClient, - getMetricsIndices, - }; + return getInfraRequestHandlerContext({ coreContext, request, plugins }); } ); // Telemetry UsageCollector.registerUsageCollector(plugins.usageCollection); + registerDataProviders({ core, plugins, libs: this.libs, logger: this.logger }); + return { inventoryViews, metricsExplorerViews, diff --git a/x-pack/solutions/observability/plugins/infra/server/utils/get_infra_request_handler_context.ts b/x-pack/solutions/observability/plugins/infra/server/utils/get_infra_request_handler_context.ts new file mode 100644 index 0000000000000..9d8d778895c38 --- /dev/null +++ b/x-pack/solutions/observability/plugins/infra/server/utils/get_infra_request_handler_context.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { CoreRequestHandlerContext, KibanaRequest } from '@kbn/core/server'; +import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; +import type { InfraPluginRequestHandlerContext } from '../types'; +import type { InfraServerPluginSetupDeps } from '../lib/adapters/framework'; + +export async function getInfraRequestHandlerContext({ + coreContext, + request, + plugins, +}: { + coreContext: CoreRequestHandlerContext; + request: KibanaRequest; + plugins: InfraServerPluginSetupDeps; +}): Promise { + const savedObjectsClient = coreContext.savedObjects.client; + const uiSettingsClient = coreContext.uiSettings.client; + const metricsClient = plugins.metricsDataAccess.client; + + const mlSystem = plugins.ml?.mlSystemProvider(request, savedObjectsClient); + const mlAnomalyDetectors = plugins.ml?.anomalyDetectorsProvider(request, savedObjectsClient); + const spaceId = plugins.spaces?.spacesService.getSpaceId(request) ?? DEFAULT_SPACE_ID; + + const getMetricsIndices = async () => { + return metricsClient.getMetricIndices({ + savedObjectsClient, + }); + }; + + return { + mlAnomalyDetectors, + mlSystem, + spaceId, + savedObjectsClient, + uiSettingsClient, + getMetricsIndices, + }; +} diff --git a/x-pack/solutions/observability/plugins/infra/tsconfig.json b/x-pack/solutions/observability/plugins/infra/tsconfig.json index a9011274ee88f..e6171578655d4 100644 --- a/x-pack/solutions/observability/plugins/infra/tsconfig.json +++ b/x-pack/solutions/observability/plugins/infra/tsconfig.json @@ -8,7 +8,7 @@ "common/**/*", "public/**/*", "server/**/*", - "types/**/*", + "types/**/*" ], "kbn_references": [ "@kbn/core", @@ -125,6 +125,7 @@ "@kbn/deeplinks-analytics", "@kbn/esql-composer", "@kbn/fleet-plugin", + "@kbn/observability-agent-builder-plugin" ], "exclude": ["target/**/*"] } diff --git a/x-pack/solutions/observability/plugins/observability_agent_builder/server/data_registry/data_registry_types.ts b/x-pack/solutions/observability/plugins/observability_agent_builder/server/data_registry/data_registry_types.ts index 6385e17c6dd9d..d6e7b745faa2f 100644 --- a/x-pack/solutions/observability/plugins/observability_agent_builder/server/data_registry/data_registry_types.ts +++ b/x-pack/solutions/observability/plugins/observability_agent_builder/server/data_registry/data_registry_types.ts @@ -104,6 +104,43 @@ interface ServicesItemsResponse { serviceOverflowCount: number; } +// Infra host types +type InfraEntityMetricType = + | 'cpu' + | 'cpuV2' + | 'normalizedLoad1m' + | 'diskSpaceUsage' + | 'memory' + | 'memoryFree' + | 'rx' + | 'tx' + | 'rxV2' + | 'txV2'; + +type InfraEntityMetadataType = 'cloud.provider' | 'host.ip' | 'host.os.name'; + +interface InfraEntityMetrics { + name: InfraEntityMetricType; + value: number | null; +} + +interface InfraEntityMetadata { + name: InfraEntityMetadataType; + value: string | number | null; +} + +interface InfraEntityMetricsItem { + name: string; + metrics: InfraEntityMetrics[]; + metadata: InfraEntityMetadata[]; + hasSystemMetrics: boolean; + alertsCount?: number; +} + +interface InfraHostsResponse { + nodes: InfraEntityMetricsItem[]; +} + export interface ObservabilityAgentBuilderDataRegistryTypes { apmErrors: (params: { request: KibanaRequest; @@ -166,4 +203,13 @@ export interface ObservabilityAgentBuilderDataRegistryTypes { end: string; searchQuery?: string; }) => Promise; + + infraHosts: (params: { + request: KibanaRequest; + from: string; + to: string; + limit: number; + kqlFilter?: string; + hostNames?: string[]; + }) => Promise; } diff --git a/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/get_hosts/README.md b/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/get_hosts/README.md new file mode 100644 index 0000000000000..590df669cf733 --- /dev/null +++ b/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/get_hosts/README.md @@ -0,0 +1,69 @@ +# get_hosts + +Retrieves a list of hosts with their infrastructure metrics (CPU, memory, disk, network). Use this tool to get an overview of host health and resource utilization. + +## When to use + +- Getting a high-level view of infrastructure health +- Identifying hosts with high CPU/memory usage or disk space issues +- Checking network throughput across hosts +- Answering questions like "which hosts are under heavy load?" or "what's the memory usage of my servers?" + +## Response + +Returns hosts with: + +- **name**: Host name +- **hasSystemMetrics**: Boolean indicating if the host has system integration metrics +- **metrics**: Array of metric objects with name and value + - `cpuV2`: CPU usage percentage (0-1) + - `memory`: Memory usage percentage (0-1) + - `memoryFree`: Free memory in bytes + - `diskSpaceUsage`: Disk usage percentage (0-1) + - `rxV2`: Network receive rate in bytes/sec + - `txV2`: Network transmit rate in bytes/sec + - `normalizedLoad1m`: Normalized 1-minute load average +- **metadata**: Host metadata + - `host.os.name`: Operating system name + - `cloud.provider`: Cloud provider (aws, gcp, azure) + - `host.ip`: Host IP address + +## Examples + +### Get hosts in the last 4 hours + +``` +POST kbn://api/agent_builder/tools/_execute +{ + "tool_id": "observability.get_hosts", + "tool_params": { + "start": "now-4h", + "end": "now" + } +} +``` + +### Get metrics for specific hosts + +``` +POST kbn://api/agent_builder/tools/_execute +{ + "tool_id": "observability.get_hosts", + "tool_params": { + "hostNames": ["web-server-01", "web-server-02"] + } +} +``` + +### Filter hosts by cloud provider + +``` +POST kbn://api/agent_builder/tools/_execute +{ + "tool_id": "observability.get_hosts", + "tool_params": { + "kqlFilter": "cloud.provider: aws", + "limit": 50 + } +} +``` diff --git a/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/get_hosts/get_hosts.ts b/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/get_hosts/get_hosts.ts new file mode 100644 index 0000000000000..620a708e8b031 --- /dev/null +++ b/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/get_hosts/get_hosts.ts @@ -0,0 +1,171 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from '@kbn/zod'; +import { ToolType } from '@kbn/onechat-common'; +import { ToolResultType } from '@kbn/onechat-common/tools/tool_result'; +import type { BuiltinToolDefinition, StaticToolRegistration } from '@kbn/onechat-server'; +import type { CoreSetup, Logger, KibanaRequest } from '@kbn/core/server'; +import { getAgentBuilderResourceAvailability } from '../../utils/get_agent_builder_resource_availability'; +import { parseDatemath } from '../../utils/time'; +import { timeRangeSchemaOptional } from '../../utils/tool_schemas'; +import type { + ObservabilityAgentBuilderPluginSetupDependencies, + ObservabilityAgentBuilderPluginStart, + ObservabilityAgentBuilderPluginStartDependencies, +} from '../../types'; +import type { ObservabilityAgentBuilderDataRegistry } from '../../data_registry/data_registry'; + +export const OBSERVABILITY_GET_HOSTS_TOOL_ID = 'observability.get_hosts'; + +const DEFAULT_TIME_RANGE = { + start: 'now-1h', + end: 'now', +}; + +const DEFAULT_LIMIT = 20; +const MAX_LIMIT = 100; + +const getHostsSchema = z.object({ + ...timeRangeSchemaOptional(DEFAULT_TIME_RANGE), + limit: z + .number() + .int() + .min(1) + .max(MAX_LIMIT) + .describe( + `Maximum number of hosts to return. Defaults to ${DEFAULT_LIMIT}, maximum is ${MAX_LIMIT}.` + ) + .optional(), + kqlFilter: z + .string() + .optional() + .describe( + 'Optional KQL filter to narrow down results. Example: "host.name: web-*" or "cloud.provider: aws".' + ), + hostNames: z + .array(z.string().min(1)) + .optional() + .describe('Optional list of specific host names to retrieve metrics for.'), +}); + +export function createGetHostsTool({ + core, + plugins, + logger, + dataRegistry, +}: { + core: CoreSetup< + ObservabilityAgentBuilderPluginStartDependencies, + ObservabilityAgentBuilderPluginStart + >; + plugins: ObservabilityAgentBuilderPluginSetupDependencies; + logger: Logger; + dataRegistry: ObservabilityAgentBuilderDataRegistry; +}): StaticToolRegistration { + const toolDefinition: BuiltinToolDefinition = { + id: OBSERVABILITY_GET_HOSTS_TOOL_ID, + type: ToolType.builtin, + description: `Retrieves a list of hosts with their infrastructure metrics (CPU, memory, disk, network). Use this tool to get an overview of host health and resource utilization. + +When to use: +- Getting a high-level view of infrastructure health +- Identifying hosts with high CPU/memory usage or disk space issues +- Checking network throughput across hosts +- Answering questions like "which hosts are under heavy load?" or "what's the memory usage of my servers?" + +Returns host names, metrics (CPU percentage, memory usage, disk space, network rx/tx), and metadata (OS name, cloud provider, IP address).`, + schema: getHostsSchema, + tags: ['observability', 'infrastructure', 'hosts', 'metrics'], + availability: { + cacheMode: 'space', + handler: async ({ request }) => { + return getAgentBuilderResourceAvailability({ core, request, logger }); + }, + }, + handler: async ( + { + start = DEFAULT_TIME_RANGE.start, + end = DEFAULT_TIME_RANGE.end, + limit = DEFAULT_LIMIT, + kqlFilter, + hostNames, + }, + { request } + ) => { + try { + const startMs = parseDatemath(start); + const endMs = parseDatemath(end, { roundUp: true }); + + if (!startMs || !endMs) { + return { + results: [ + { + type: ToolResultType.error, + data: { + message: 'Invalid date range provided.', + }, + }, + ], + }; + } + + const result = await dataRegistry.getData('infraHosts', { + request: request as KibanaRequest, + from: new Date(startMs).toISOString(), + to: new Date(endMs).toISOString(), + limit, + kqlFilter, + hostNames, + }); + + if (!result) { + return { + results: [ + { + type: ToolResultType.error, + data: { + message: + 'Host data is not available. The infra plugin may not be installed or configured.', + }, + }, + ], + }; + } + + return { + results: [ + { + type: ToolResultType.other, + data: { + hosts: result.nodes, + total: result.nodes.length, + }, + }, + ], + }; + } catch (error) { + logger.error(`Error fetching hosts: ${error.message}`); + logger.debug(error); + + return { + results: [ + { + type: ToolResultType.error, + data: { + message: `Failed to fetch hosts: ${error.message}`, + stack: error.stack, + }, + }, + ], + }; + } + }, + }; + + return toolDefinition; +} diff --git a/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/index.ts b/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/index.ts index c661926547f27..e11bc44e53fbd 100644 --- a/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/index.ts +++ b/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/index.ts @@ -13,3 +13,4 @@ export { OBSERVABILITY_GET_ANOMALY_DETECTION_JOBS_TOOL_ID } from './get_anomaly_ export { OBSERVABILITY_GET_SERVICES_TOOL_ID } from './get_services/get_services'; export { OBSERVABILITY_GET_DOWNSTREAM_DEPENDENCIES_TOOL_ID } from './get_downstream_dependencies/get_downstream_dependencies'; export { OBSERVABILITY_GET_CORRELATED_LOGS_TOOL_ID } from './get_correlated_logs/get_correlated_logs'; +export { OBSERVABILITY_GET_HOSTS_TOOL_ID } from './get_hosts/get_hosts'; diff --git a/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/register_tools.ts b/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/register_tools.ts index d982ab779c4e6..ea9a13e04c3eb 100644 --- a/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/register_tools.ts +++ b/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/register_tools.ts @@ -35,6 +35,7 @@ import { OBSERVABILITY_GET_CORRELATED_LOGS_TOOL_ID, createGetCorrelatedLogsTool, } from './get_correlated_logs/get_correlated_logs'; +import { OBSERVABILITY_GET_HOSTS_TOOL_ID, createGetHostsTool } from './get_hosts/get_hosts'; import { createGetServicesTool, OBSERVABILITY_GET_SERVICES_TOOL_ID, @@ -61,6 +62,7 @@ const OBSERVABILITY_TOOL_IDS = [ OBSERVABILITY_GET_CORRELATED_LOGS_TOOL_ID, OBSERVABILITY_GET_SERVICES_TOOL_ID, OBSERVABILITY_GET_DOWNSTREAM_DEPENDENCIES_TOOL_ID, + OBSERVABILITY_GET_HOSTS_TOOL_ID, ]; export const OBSERVABILITY_AGENT_TOOL_IDS = [...PLATFORM_TOOL_IDS, ...OBSERVABILITY_TOOL_IDS]; @@ -88,6 +90,7 @@ export async function registerTools({ createGetServicesTool({ core, dataRegistry, logger }), createDownstreamDependenciesTool({ core, dataRegistry, logger }), createGetCorrelatedLogsTool({ core, logger }), + createGetHostsTool({ core, plugins, logger, dataRegistry }), ]; for (const tool of observabilityTools) { diff --git a/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/observability_agent_builder/index.ts b/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/observability_agent_builder/index.ts index 52a07553da2f2..ef7a4d2fb19d5 100644 --- a/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/observability_agent_builder/index.ts +++ b/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/observability_agent_builder/index.ts @@ -17,6 +17,7 @@ export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) loadTestFile(require.resolve('./tools/run_log_rate_analysis.spec.ts')); loadTestFile(require.resolve('./tools/get_log_categories.spec.ts')); loadTestFile(require.resolve('./tools/get_correlated_logs.spec.ts')); + loadTestFile(require.resolve('./tools/get_hosts.spec.ts')); loadTestFile(require.resolve('./ai_insights/error.spec.ts')); loadTestFile(require.resolve('./ai_insights/alert.spec.ts')); }); diff --git a/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/observability_agent_builder/tools/get_hosts.spec.ts b/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/observability_agent_builder/tools/get_hosts.spec.ts new file mode 100644 index 0000000000000..faef2834527a1 --- /dev/null +++ b/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/observability_agent_builder/tools/get_hosts.spec.ts @@ -0,0 +1,186 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import type { InfraSynthtraceEsClient } from '@kbn/synthtrace'; +import type { OtherResult } from '@kbn/onechat-common'; +import { OBSERVABILITY_GET_HOSTS_TOOL_ID } from '@kbn/observability-agent-builder-plugin/server/tools'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../ftr_provider_context'; +import { createAgentBuilderApiClient } from '../utils/agent_builder_client'; +import { createSyntheticInfraData } from '../utils/synthtrace_scenarios'; + +export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { + const roleScopedSupertest = getService('roleScopedSupertest'); + + describe(`tool: ${OBSERVABILITY_GET_HOSTS_TOOL_ID}`, function () { + let agentBuilderApiClient: ReturnType; + let infraSynthtraceEsClient: InfraSynthtraceEsClient; + + before(async () => { + const scoped = await roleScopedSupertest.getSupertestWithRoleScope('editor'); + agentBuilderApiClient = createAgentBuilderApiClient(scoped); + + ({ infraSynthtraceEsClient } = await createSyntheticInfraData({ + getService, + hostNames: ['test-host-01', 'test-host-02'], + })); + }); + + after(async () => { + if (infraSynthtraceEsClient) { + await infraSynthtraceEsClient.clean(); + } + }); + + interface ResponseData { + total: number; + hosts: Array<{ + name: string; + metrics: Array<{ name: string; value: number | null }>; + metadata: Array<{ name: string; value: string | number | null }>; + }>; + } + + describe('when fetching hosts', () => { + let responseData: ResponseData; + + before(async () => { + const results = await agentBuilderApiClient.executeTool>({ + id: OBSERVABILITY_GET_HOSTS_TOOL_ID, + params: { + start: 'now-1h', + end: 'now', + }, + }); + + expect(results).to.have.length(1); + + responseData = results[0].data; + }); + + it('returns the correct total count', () => { + expect(responseData.total).to.be(2); + }); + + it('returns the expected hosts', () => { + const hostNames = responseData.hosts.map((host) => host.name); + expect(hostNames).to.eql(['test-host-01', 'test-host-02']); + }); + + it('includes metrics for each host', () => { + for (const host of responseData.hosts) { + expect(host).to.have.property('metrics'); + expect(host.metrics).to.be.an('array'); + } + }); + + it('includes metadata for each host', () => { + for (const host of responseData.hosts) { + expect(host).to.have.property('metadata'); + expect(host.metadata).to.be.an('array'); + } + }); + + it('returns correct CPU metrics', () => { + const host01Cpu = responseData.hosts + .find((h) => h.name === 'test-host-01') + ?.metrics.find((m) => m.name === 'cpuV2'); + + expect(host01Cpu).to.be.ok(); + expect(host01Cpu!.value).to.be.within(0.6, 0.7); + + const host02Cpu = responseData.hosts + .find((h) => h.name === 'test-host-02') + ?.metrics.find((m) => m.name === 'cpuV2'); + + expect(host02Cpu).to.be.ok(); + expect(host02Cpu!.value).to.be.within(0.3, 0.4); + }); + + it('returns correct memory metrics', () => { + const host01Memory = responseData.hosts + .find((h) => h.name === 'test-host-01') + ?.metrics.find((m) => m.name === 'memory'); + + expect(host01Memory).to.be.ok(); + expect(host01Memory!.value).to.be.within(0.7, 0.75); + + const host02Memory = responseData.hosts + .find((h) => h.name === 'test-host-02') + ?.metrics.find((m) => m.name === 'memory'); + + expect(host02Memory).to.be.ok(); + expect(host02Memory!.value).to.be.within(0.8, 0.9); + }); + + it('returns correct disk metrics', () => { + const host01Cpu = responseData.hosts + .find((h) => h.name === 'test-host-01') + ?.metrics.find((m) => m.name === 'diskSpaceUsage'); + expect(host01Cpu).to.be.ok(); + expect(host01Cpu!.value).to.be.within(0.4, 0.5); + + const host02Cpu = responseData.hosts + .find((h) => h.name === 'test-host-02') + ?.metrics.find((m) => m.name === 'diskSpaceUsage'); + expect(host02Cpu).to.be.ok(); + expect(host02Cpu!.value).to.be.within(0.65, 0.7); + }); + }); + + describe('when using limit parameter', () => { + it('respects the limit and returns fewer hosts', async () => { + const results = await agentBuilderApiClient.executeTool>({ + id: OBSERVABILITY_GET_HOSTS_TOOL_ID, + params: { + start: 'now-1h', + end: 'now', + limit: 1, + }, + }); + + expect(results).to.have.length(1); + expect(results[0].data.hosts).to.have.length(1); + expect(results[0].data.total).to.be(1); + }); + }); + + describe('when using hostNames parameter', () => { + it('filters to specific hosts', async () => { + const results = await agentBuilderApiClient.executeTool>({ + id: OBSERVABILITY_GET_HOSTS_TOOL_ID, + params: { + start: 'now-1h', + end: 'now', + hostNames: ['test-host-01'], + }, + }); + + expect(results).to.have.length(1); + expect(results[0].data.hosts).to.have.length(1); + expect(results[0].data.hosts[0].name).to.be('test-host-01'); + }); + }); + + describe('when using kqlFilter parameter', () => { + it('filters hosts by KQL query', async () => { + const results = await agentBuilderApiClient.executeTool>({ + id: OBSERVABILITY_GET_HOSTS_TOOL_ID, + params: { + start: 'now-1h', + end: 'now', + kqlFilter: 'host.name: test-host-02', + }, + }); + + expect(results).to.have.length(1); + expect(results[0].data.hosts).to.have.length(1); + expect(results[0].data.hosts[0].name).to.be('test-host-02'); + }); + }); + }); +} diff --git a/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/observability_agent_builder/utils/synthtrace_scenarios/create_synthetic_infra_data.ts b/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/observability_agent_builder/utils/synthtrace_scenarios/create_synthetic_infra_data.ts new file mode 100644 index 0000000000000..ba71168b12905 --- /dev/null +++ b/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/observability_agent_builder/utils/synthtrace_scenarios/create_synthetic_infra_data.ts @@ -0,0 +1,119 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import moment from 'moment'; +import { infra, timerange } from '@kbn/synthtrace-client'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; + +interface HostConfig { + name: string; + cpuUsage: number; + memoryUsage: number; + diskUsage: number; + cloudProvider: string; + cloudRegion: string; +} + +const DEFAULT_HOSTS: HostConfig[] = [ + { + name: 'web-server-01', + cpuUsage: 0.65, + memoryUsage: 0.72, + diskUsage: 0.45, + cloudProvider: 'aws', + cloudRegion: 'us-east-1', + }, + { + name: 'db-server-01', + cpuUsage: 0.35, + memoryUsage: 0.85, + diskUsage: 0.68, + cloudProvider: 'aws', + cloudRegion: 'us-east-1', + }, + { + name: 'api-server-01', + cpuUsage: 0.45, + memoryUsage: 0.55, + diskUsage: 0.25, + cloudProvider: 'gcp', + cloudRegion: 'us-central1', + }, +]; + +/** + * Creates synthetic infrastructure data for testing the get_hosts tool. + */ +export const createSyntheticInfraData = async ({ + getService, + hostNames, +}: { + getService: DeploymentAgnosticFtrProviderContext['getService']; + hostNames?: string[]; +}) => { + const synthtrace = getService('synthtrace'); + const infraSynthtraceEsClient = synthtrace.createInfraSynthtraceEsClient(); + + await infraSynthtraceEsClient.clean(); + + const from = moment().subtract(15, 'minutes'); + const to = moment(); + + // Use custom host names with default profiles, or use defaults + const hostConfigs = hostNames + ? hostNames.map((name, idx) => ({ + ...DEFAULT_HOSTS[idx % DEFAULT_HOSTS.length], + name, + })) + : DEFAULT_HOSTS; + + await infraSynthtraceEsClient.index( + timerange(from, to) + .interval('30s') + .rate(1) + .generator((timestamp) => + hostConfigs.flatMap((hostConfig) => { + const host = infra.host(hostConfig.name); + const totalMemory = 68_719_476_736; // 64GB + const usedMemory = Math.floor(totalMemory * hostConfig.memoryUsage); + + const defaults = { + 'agent.id': 'synthtrace', + 'host.name': hostConfig.name, + 'host.hostname': hostConfig.name, + 'cloud.provider': hostConfig.cloudProvider, + 'cloud.region': hostConfig.cloudRegion, + }; + + return [ + host + .cpu({ 'system.cpu.total.norm.pct': hostConfig.cpuUsage }) + .defaults(defaults) + .timestamp(timestamp), + host + .memory({ + 'system.memory.actual.free': totalMemory - usedMemory, + 'system.memory.actual.used.bytes': usedMemory, + 'system.memory.actual.used.pct': hostConfig.memoryUsage, + 'system.memory.total': totalMemory, + }) + .defaults(defaults) + .timestamp(timestamp), + host.network().defaults(defaults).timestamp(timestamp), + host.load().defaults(defaults).timestamp(timestamp), + host + .filesystem({ 'system.filesystem.used.pct': hostConfig.diskUsage }) + .defaults(defaults) + .timestamp(timestamp), + host.diskio().defaults(defaults).timestamp(timestamp), + ]; + }) + ) + ); + + return { infraSynthtraceEsClient }; +}; diff --git a/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/observability_agent_builder/utils/synthtrace_scenarios/index.ts b/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/observability_agent_builder/utils/synthtrace_scenarios/index.ts index 6a79f59f2fc21..14b4d7fdeca9c 100644 --- a/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/observability_agent_builder/utils/synthtrace_scenarios/index.ts +++ b/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/observability_agent_builder/utils/synthtrace_scenarios/index.ts @@ -11,3 +11,4 @@ export * from './create_log_rate_analysis_spike_data'; export * from './create_synthetic_logs_with_categories'; export * from './create_synthetic_logs_with_errors_and_correlation_ids'; export * from './create_distributed_trace_with_errors'; +export * from './create_synthetic_infra_data'; From 261219667818faaf63b5f91eb7cf2f1a37fb2905 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 16 Dec 2025 13:27:27 +0000 Subject: [PATCH 2/4] Changes from node scripts/regenerate_moon_projects.js --update --- x-pack/solutions/observability/plugins/infra/moon.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/solutions/observability/plugins/infra/moon.yml b/x-pack/solutions/observability/plugins/infra/moon.yml index bd5cc99a8723e..74c695f8f01a6 100644 --- a/x-pack/solutions/observability/plugins/infra/moon.yml +++ b/x-pack/solutions/observability/plugins/infra/moon.yml @@ -132,6 +132,7 @@ dependsOn: - '@kbn/deeplinks-analytics' - '@kbn/esql-composer' - '@kbn/fleet-plugin' + - '@kbn/observability-agent-builder-plugin' tags: - plugin - prod From 86e62af88398b32e45a5dffba0a8488b592e2442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Tue, 16 Dec 2025 22:07:55 +0100 Subject: [PATCH 3/4] Improve types and address feedback --- .../onechat-common/tools/tool_result.ts | 4 +- .../onechat/onechat-server/tools/handler.ts | 9 ++- .../onechat/onechat-server/tools/index.ts | 1 + .../server/tools/get_hosts/README.md | 19 ------ .../server/tools/get_hosts/get_hosts.ts | 23 +++++-- .../server/tools/register_tools.ts | 2 +- .../tools/get_hosts.spec.ts | 66 +++++++++++-------- .../create_synthetic_infra_data.ts | 43 ++---------- 8 files changed, 69 insertions(+), 98 deletions(-) diff --git a/x-pack/platform/packages/shared/onechat/onechat-common/tools/tool_result.ts b/x-pack/platform/packages/shared/onechat/onechat-common/tools/tool_result.ts index 89242bb54efdb..c3911f4414509 100644 --- a/x-pack/platform/packages/shared/onechat/onechat-common/tools/tool_result.ts +++ b/x-pack/platform/packages/shared/onechat/onechat-common/tools/tool_result.ts @@ -89,13 +89,13 @@ export type ErrorResult = ToolResultMixin< } >; -export type ToolResult = +export type ToolResult> = | ResourceResult | TabularDataResult | QueryResult | VisualizationResult | DashboardResult - | OtherResult + | OtherResult | ErrorResult; export const isResourceResult = (result: ToolResult): result is ResourceResult => { diff --git a/x-pack/platform/packages/shared/onechat/onechat-server/tools/handler.ts b/x-pack/platform/packages/shared/onechat/onechat-server/tools/handler.ts index 133bd36f4a710..0718b09a29597 100644 --- a/x-pack/platform/packages/shared/onechat/onechat-server/tools/handler.ts +++ b/x-pack/platform/packages/shared/onechat/onechat-server/tools/handler.ts @@ -21,13 +21,16 @@ import type { /** * Tool result as returned by the tool handler. */ -export type ToolHandlerResult = Omit & { tool_result_id?: string }; +export type ToolHandlerResult = Omit< + TResult, + 'tool_result_id' +> & { tool_result_id?: string }; /** * Return value for {@link ToolHandlerFn} / {@link BuiltinToolDefinition} */ -export interface ToolHandlerReturn { - results: ToolHandlerResult[]; +export interface ToolHandlerReturn { + results: Array>; } /** diff --git a/x-pack/platform/packages/shared/onechat/onechat-server/tools/index.ts b/x-pack/platform/packages/shared/onechat/onechat-server/tools/index.ts index e75d4fb1e483b..54b3e409b9783 100644 --- a/x-pack/platform/packages/shared/onechat/onechat-server/tools/index.ts +++ b/x-pack/platform/packages/shared/onechat/onechat-server/tools/index.ts @@ -21,6 +21,7 @@ export type { ToolHandlerReturn, ToolHandlerContext, ToolHandlerResult, + OtherResultReturn, } from './handler'; export { getToolResultId, createErrorResult, isToolResultId } from './utils'; export type { InternalToolDefinition, InternalToolAvailabilityHandler } from './internal'; diff --git a/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/get_hosts/README.md b/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/get_hosts/README.md index 590df669cf733..99eae45fd80d9 100644 --- a/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/get_hosts/README.md +++ b/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/get_hosts/README.md @@ -9,25 +9,6 @@ Retrieves a list of hosts with their infrastructure metrics (CPU, memory, disk, - Checking network throughput across hosts - Answering questions like "which hosts are under heavy load?" or "what's the memory usage of my servers?" -## Response - -Returns hosts with: - -- **name**: Host name -- **hasSystemMetrics**: Boolean indicating if the host has system integration metrics -- **metrics**: Array of metric objects with name and value - - `cpuV2`: CPU usage percentage (0-1) - - `memory`: Memory usage percentage (0-1) - - `memoryFree`: Free memory in bytes - - `diskSpaceUsage`: Disk usage percentage (0-1) - - `rxV2`: Network receive rate in bytes/sec - - `txV2`: Network transmit rate in bytes/sec - - `normalizedLoad1m`: Normalized 1-minute load average -- **metadata**: Host metadata - - `host.os.name`: Operating system name - - `cloud.provider`: Cloud provider (aws, gcp, azure) - - `host.ip`: Host IP address - ## Examples ### Get hosts in the last 4 hours diff --git a/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/get_hosts/get_hosts.ts b/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/get_hosts/get_hosts.ts index 620a708e8b031..ddb9e34ff38a5 100644 --- a/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/get_hosts/get_hosts.ts +++ b/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/get_hosts/get_hosts.ts @@ -7,14 +7,18 @@ import { z } from '@kbn/zod'; import { ToolType } from '@kbn/onechat-common'; +import type { ErrorResult, OtherResult } from '@kbn/onechat-common/tools/tool_result'; import { ToolResultType } from '@kbn/onechat-common/tools/tool_result'; -import type { BuiltinToolDefinition, StaticToolRegistration } from '@kbn/onechat-server'; +import type { + BuiltinToolDefinition, + StaticToolRegistration, + ToolHandlerReturn, +} from '@kbn/onechat-server'; import type { CoreSetup, Logger, KibanaRequest } from '@kbn/core/server'; import { getAgentBuilderResourceAvailability } from '../../utils/get_agent_builder_resource_availability'; import { parseDatemath } from '../../utils/time'; import { timeRangeSchemaOptional } from '../../utils/tool_schemas'; import type { - ObservabilityAgentBuilderPluginSetupDependencies, ObservabilityAgentBuilderPluginStart, ObservabilityAgentBuilderPluginStartDependencies, } from '../../types'; @@ -30,6 +34,15 @@ const DEFAULT_TIME_RANGE = { const DEFAULT_LIMIT = 20; const MAX_LIMIT = 100; +export type GetHostsToolResult = OtherResult<{ + total: number; + hosts: Array<{ + name: string; + metrics: Array<{ name: string; value: number | null }>; + metadata: Array<{ name: string; value: string | number | null }>; + }>; +}>; + const getHostsSchema = z.object({ ...timeRangeSchemaOptional(DEFAULT_TIME_RANGE), limit: z @@ -55,7 +68,6 @@ const getHostsSchema = z.object({ export function createGetHostsTool({ core, - plugins, logger, dataRegistry, }: { @@ -63,7 +75,6 @@ export function createGetHostsTool({ ObservabilityAgentBuilderPluginStartDependencies, ObservabilityAgentBuilderPluginStart >; - plugins: ObservabilityAgentBuilderPluginSetupDependencies; logger: Logger; dataRegistry: ObservabilityAgentBuilderDataRegistry; }): StaticToolRegistration { @@ -96,7 +107,7 @@ Returns host names, metrics (CPU percentage, memory usage, disk space, network r hostNames, }, { request } - ) => { + ): Promise> => { try { const startMs = parseDatemath(start); const endMs = parseDatemath(end, { roundUp: true }); @@ -140,7 +151,7 @@ Returns host names, metrics (CPU percentage, memory usage, disk space, network r return { results: [ { - type: ToolResultType.other, + type: ToolResultType.other as const, data: { hosts: result.nodes, total: result.nodes.length, diff --git a/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/register_tools.ts b/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/register_tools.ts index ea9a13e04c3eb..b0d4c26d385c5 100644 --- a/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/register_tools.ts +++ b/x-pack/solutions/observability/plugins/observability_agent_builder/server/tools/register_tools.ts @@ -90,7 +90,7 @@ export async function registerTools({ createGetServicesTool({ core, dataRegistry, logger }), createDownstreamDependenciesTool({ core, dataRegistry, logger }), createGetCorrelatedLogsTool({ core, logger }), - createGetHostsTool({ core, plugins, logger, dataRegistry }), + createGetHostsTool({ core, logger, dataRegistry }), ]; for (const tool of observabilityTools) { diff --git a/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/observability_agent_builder/tools/get_hosts.spec.ts b/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/observability_agent_builder/tools/get_hosts.spec.ts index faef2834527a1..eaefba57d7562 100644 --- a/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/observability_agent_builder/tools/get_hosts.spec.ts +++ b/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/observability_agent_builder/tools/get_hosts.spec.ts @@ -7,11 +7,11 @@ import expect from '@kbn/expect'; import type { InfraSynthtraceEsClient } from '@kbn/synthtrace'; -import type { OtherResult } from '@kbn/onechat-common'; import { OBSERVABILITY_GET_HOSTS_TOOL_ID } from '@kbn/observability-agent-builder-plugin/server/tools'; +import type { GetHostsToolResult } from '@kbn/observability-agent-builder-plugin/server/tools/get_hosts/get_hosts'; import type { DeploymentAgnosticFtrProviderContext } from '../../../ftr_provider_context'; import { createAgentBuilderApiClient } from '../utils/agent_builder_client'; -import { createSyntheticInfraData } from '../utils/synthtrace_scenarios'; +import { createSyntheticInfraData, type HostConfig } from '../utils/synthtrace_scenarios'; export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { const roleScopedSupertest = getService('roleScopedSupertest'); @@ -24,9 +24,28 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { const scoped = await roleScopedSupertest.getSupertestWithRoleScope('editor'); agentBuilderApiClient = createAgentBuilderApiClient(scoped); + const testHosts: HostConfig[] = [ + { + name: 'test-host-01', + cpuUsage: 0.65, + memoryUsage: 0.72, + diskUsage: 0.45, + cloudProvider: 'aws', + cloudRegion: 'us-east-1', + }, + { + name: 'test-host-02', + cpuUsage: 0.35, + memoryUsage: 0.85, + diskUsage: 0.68, + cloudProvider: 'gcp', + cloudRegion: 'us-central1', + }, + ]; + ({ infraSynthtraceEsClient } = await createSyntheticInfraData({ getService, - hostNames: ['test-host-01', 'test-host-02'], + hosts: testHosts, })); }); @@ -36,20 +55,11 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { } }); - interface ResponseData { - total: number; - hosts: Array<{ - name: string; - metrics: Array<{ name: string; value: number | null }>; - metadata: Array<{ name: string; value: string | number | null }>; - }>; - } - describe('when fetching hosts', () => { - let responseData: ResponseData; + let resultData: GetHostsToolResult['data']; before(async () => { - const results = await agentBuilderApiClient.executeTool>({ + const results = await agentBuilderApiClient.executeTool({ id: OBSERVABILITY_GET_HOSTS_TOOL_ID, params: { start: 'now-1h', @@ -59,41 +69,41 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { expect(results).to.have.length(1); - responseData = results[0].data; + resultData = results[0].data; }); it('returns the correct total count', () => { - expect(responseData.total).to.be(2); + expect(resultData.total).to.be(2); }); it('returns the expected hosts', () => { - const hostNames = responseData.hosts.map((host) => host.name); + const hostNames = resultData.hosts.map((host) => host.name); expect(hostNames).to.eql(['test-host-01', 'test-host-02']); }); it('includes metrics for each host', () => { - for (const host of responseData.hosts) { + for (const host of resultData.hosts) { expect(host).to.have.property('metrics'); expect(host.metrics).to.be.an('array'); } }); it('includes metadata for each host', () => { - for (const host of responseData.hosts) { + for (const host of resultData.hosts) { expect(host).to.have.property('metadata'); expect(host.metadata).to.be.an('array'); } }); it('returns correct CPU metrics', () => { - const host01Cpu = responseData.hosts + const host01Cpu = resultData.hosts .find((h) => h.name === 'test-host-01') ?.metrics.find((m) => m.name === 'cpuV2'); expect(host01Cpu).to.be.ok(); expect(host01Cpu!.value).to.be.within(0.6, 0.7); - const host02Cpu = responseData.hosts + const host02Cpu = resultData.hosts .find((h) => h.name === 'test-host-02') ?.metrics.find((m) => m.name === 'cpuV2'); @@ -102,14 +112,14 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { }); it('returns correct memory metrics', () => { - const host01Memory = responseData.hosts + const host01Memory = resultData.hosts .find((h) => h.name === 'test-host-01') ?.metrics.find((m) => m.name === 'memory'); expect(host01Memory).to.be.ok(); expect(host01Memory!.value).to.be.within(0.7, 0.75); - const host02Memory = responseData.hosts + const host02Memory = resultData.hosts .find((h) => h.name === 'test-host-02') ?.metrics.find((m) => m.name === 'memory'); @@ -118,13 +128,13 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { }); it('returns correct disk metrics', () => { - const host01Cpu = responseData.hosts + const host01Cpu = resultData.hosts .find((h) => h.name === 'test-host-01') ?.metrics.find((m) => m.name === 'diskSpaceUsage'); expect(host01Cpu).to.be.ok(); expect(host01Cpu!.value).to.be.within(0.4, 0.5); - const host02Cpu = responseData.hosts + const host02Cpu = resultData.hosts .find((h) => h.name === 'test-host-02') ?.metrics.find((m) => m.name === 'diskSpaceUsage'); expect(host02Cpu).to.be.ok(); @@ -134,7 +144,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { describe('when using limit parameter', () => { it('respects the limit and returns fewer hosts', async () => { - const results = await agentBuilderApiClient.executeTool>({ + const results = await agentBuilderApiClient.executeTool({ id: OBSERVABILITY_GET_HOSTS_TOOL_ID, params: { start: 'now-1h', @@ -151,7 +161,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { describe('when using hostNames parameter', () => { it('filters to specific hosts', async () => { - const results = await agentBuilderApiClient.executeTool>({ + const results = await agentBuilderApiClient.executeTool({ id: OBSERVABILITY_GET_HOSTS_TOOL_ID, params: { start: 'now-1h', @@ -168,7 +178,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { describe('when using kqlFilter parameter', () => { it('filters hosts by KQL query', async () => { - const results = await agentBuilderApiClient.executeTool>({ + const results = await agentBuilderApiClient.executeTool({ id: OBSERVABILITY_GET_HOSTS_TOOL_ID, params: { start: 'now-1h', diff --git a/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/observability_agent_builder/utils/synthtrace_scenarios/create_synthetic_infra_data.ts b/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/observability_agent_builder/utils/synthtrace_scenarios/create_synthetic_infra_data.ts index ba71168b12905..ea6ca4423cd42 100644 --- a/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/observability_agent_builder/utils/synthtrace_scenarios/create_synthetic_infra_data.ts +++ b/x-pack/solutions/observability/test/api_integration_deployment_agnostic/apis/observability_agent_builder/utils/synthtrace_scenarios/create_synthetic_infra_data.ts @@ -9,7 +9,7 @@ import moment from 'moment'; import { infra, timerange } from '@kbn/synthtrace-client'; import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; -interface HostConfig { +export interface HostConfig { name: string; cpuUsage: number; memoryUsage: number; @@ -18,42 +18,15 @@ interface HostConfig { cloudRegion: string; } -const DEFAULT_HOSTS: HostConfig[] = [ - { - name: 'web-server-01', - cpuUsage: 0.65, - memoryUsage: 0.72, - diskUsage: 0.45, - cloudProvider: 'aws', - cloudRegion: 'us-east-1', - }, - { - name: 'db-server-01', - cpuUsage: 0.35, - memoryUsage: 0.85, - diskUsage: 0.68, - cloudProvider: 'aws', - cloudRegion: 'us-east-1', - }, - { - name: 'api-server-01', - cpuUsage: 0.45, - memoryUsage: 0.55, - diskUsage: 0.25, - cloudProvider: 'gcp', - cloudRegion: 'us-central1', - }, -]; - /** * Creates synthetic infrastructure data for testing the get_hosts tool. */ export const createSyntheticInfraData = async ({ getService, - hostNames, + hosts, }: { getService: DeploymentAgnosticFtrProviderContext['getService']; - hostNames?: string[]; + hosts: HostConfig[]; }) => { const synthtrace = getService('synthtrace'); const infraSynthtraceEsClient = synthtrace.createInfraSynthtraceEsClient(); @@ -63,20 +36,12 @@ export const createSyntheticInfraData = async ({ const from = moment().subtract(15, 'minutes'); const to = moment(); - // Use custom host names with default profiles, or use defaults - const hostConfigs = hostNames - ? hostNames.map((name, idx) => ({ - ...DEFAULT_HOSTS[idx % DEFAULT_HOSTS.length], - name, - })) - : DEFAULT_HOSTS; - await infraSynthtraceEsClient.index( timerange(from, to) .interval('30s') .rate(1) .generator((timestamp) => - hostConfigs.flatMap((hostConfig) => { + hosts.flatMap((hostConfig) => { const host = infra.host(hostConfig.name); const totalMemory = 68_719_476_736; // 64GB const usedMemory = Math.floor(totalMemory * hostConfig.memoryUsage); From bfced5431612d0004b60f510bc9a41649d1dc92b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Wed, 17 Dec 2025 11:22:10 +0100 Subject: [PATCH 4/4] Remove invalid export --- .../packages/shared/onechat/onechat-server/tools/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/platform/packages/shared/onechat/onechat-server/tools/index.ts b/x-pack/platform/packages/shared/onechat/onechat-server/tools/index.ts index 54b3e409b9783..e75d4fb1e483b 100644 --- a/x-pack/platform/packages/shared/onechat/onechat-server/tools/index.ts +++ b/x-pack/platform/packages/shared/onechat/onechat-server/tools/index.ts @@ -21,7 +21,6 @@ export type { ToolHandlerReturn, ToolHandlerContext, ToolHandlerResult, - OtherResultReturn, } from './handler'; export { getToolResultId, createErrorResult, isToolResultId } from './utils'; export type { InternalToolDefinition, InternalToolAvailabilityHandler } from './internal';