holmesgpt 0.14.1a0__py3-none-any.whl → 0.14.3a0__py3-none-any.whl
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.
Potentially problematic release.
This version of holmesgpt might be problematic. Click here for more details.
- holmes/__init__.py +1 -1
- holmes/clients/robusta_client.py +5 -2
- holmes/common/env_vars.py +8 -2
- holmes/config.py +4 -7
- holmes/core/conversations.py +12 -2
- holmes/core/feedback.py +191 -0
- holmes/core/llm.py +52 -10
- holmes/core/models.py +101 -1
- holmes/core/supabase_dal.py +23 -9
- holmes/core/tool_calling_llm.py +206 -16
- holmes/core/tools.py +20 -7
- holmes/core/tools_utils/token_counting.py +13 -0
- holmes/core/tools_utils/tool_context_window_limiter.py +45 -23
- holmes/core/tools_utils/tool_executor.py +11 -6
- holmes/core/toolset_manager.py +7 -3
- holmes/core/truncation/dal_truncation_utils.py +23 -0
- holmes/interactive.py +146 -14
- holmes/plugins/prompts/_fetch_logs.jinja2 +13 -1
- holmes/plugins/runbooks/__init__.py +6 -1
- holmes/plugins/toolsets/__init__.py +11 -4
- holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py +9 -20
- holmes/plugins/toolsets/azure_sql/tools/analyze_connection_failures.py +2 -3
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_connections.py +2 -3
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_health_status.py +6 -4
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_performance.py +6 -4
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_storage.py +2 -3
- holmes/plugins/toolsets/azure_sql/tools/get_active_alerts.py +6 -4
- holmes/plugins/toolsets/azure_sql/tools/get_slow_queries.py +2 -3
- holmes/plugins/toolsets/azure_sql/tools/get_top_cpu_queries.py +2 -3
- holmes/plugins/toolsets/azure_sql/tools/get_top_data_io_queries.py +2 -3
- holmes/plugins/toolsets/azure_sql/tools/get_top_log_io_queries.py +2 -3
- holmes/plugins/toolsets/bash/bash_toolset.py +4 -7
- holmes/plugins/toolsets/cilium.yaml +284 -0
- holmes/plugins/toolsets/datadog/datadog_api.py +490 -24
- holmes/plugins/toolsets/datadog/datadog_logs_instructions.jinja2 +21 -10
- holmes/plugins/toolsets/datadog/toolset_datadog_general.py +333 -199
- holmes/plugins/toolsets/datadog/toolset_datadog_logs.py +181 -9
- holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py +80 -22
- holmes/plugins/toolsets/datadog/toolset_datadog_rds.py +5 -8
- holmes/plugins/toolsets/datadog/toolset_datadog_traces.py +7 -12
- holmes/plugins/toolsets/git.py +14 -12
- holmes/plugins/toolsets/grafana/grafana_tempo_api.py +23 -42
- holmes/plugins/toolsets/grafana/toolset_grafana.py +2 -3
- holmes/plugins/toolsets/grafana/toolset_grafana_loki.py +2 -1
- holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py +21 -39
- holmes/plugins/toolsets/internet/internet.py +2 -3
- holmes/plugins/toolsets/internet/notion.py +2 -3
- holmes/plugins/toolsets/investigator/core_investigation.py +7 -9
- holmes/plugins/toolsets/kafka.py +7 -18
- holmes/plugins/toolsets/logging_utils/logging_api.py +80 -4
- holmes/plugins/toolsets/mcp/toolset_mcp.py +2 -3
- holmes/plugins/toolsets/newrelic/__init__.py +0 -0
- holmes/plugins/toolsets/newrelic/new_relic_api.py +125 -0
- holmes/plugins/toolsets/newrelic/newrelic.jinja2 +41 -0
- holmes/plugins/toolsets/newrelic/newrelic.py +211 -0
- holmes/plugins/toolsets/opensearch/opensearch.py +5 -12
- holmes/plugins/toolsets/opensearch/opensearch_traces.py +3 -6
- holmes/plugins/toolsets/prometheus/prometheus.py +808 -419
- holmes/plugins/toolsets/prometheus/prometheus_instructions.jinja2 +27 -11
- holmes/plugins/toolsets/rabbitmq/toolset_rabbitmq.py +3 -6
- holmes/plugins/toolsets/robusta/robusta.py +4 -9
- holmes/plugins/toolsets/runbook/runbook_fetcher.py +93 -13
- holmes/plugins/toolsets/servicenow/servicenow.py +5 -10
- holmes/utils/sentry_helper.py +1 -1
- holmes/utils/stream.py +22 -7
- holmes/version.py +34 -14
- {holmesgpt-0.14.1a0.dist-info → holmesgpt-0.14.3a0.dist-info}/METADATA +7 -9
- {holmesgpt-0.14.1a0.dist-info → holmesgpt-0.14.3a0.dist-info}/RECORD +71 -65
- holmes/core/tools_utils/data_types.py +0 -81
- holmes/plugins/toolsets/newrelic.py +0 -231
- {holmesgpt-0.14.1a0.dist-info → holmesgpt-0.14.3a0.dist-info}/LICENSE.txt +0 -0
- {holmesgpt-0.14.1a0.dist-info → holmesgpt-0.14.3a0.dist-info}/WHEEL +0 -0
- {holmesgpt-0.14.1a0.dist-info → holmesgpt-0.14.3a0.dist-info}/entry_points.txt +0 -0
|
@@ -10,6 +10,7 @@ from urllib.parse import urlparse
|
|
|
10
10
|
from holmes.core.tools import (
|
|
11
11
|
CallablePrerequisite,
|
|
12
12
|
Tool,
|
|
13
|
+
ToolInvokeContext,
|
|
13
14
|
ToolParameter,
|
|
14
15
|
Toolset,
|
|
15
16
|
StructuredToolResult,
|
|
@@ -23,85 +24,120 @@ from holmes.plugins.toolsets.datadog.datadog_api import (
|
|
|
23
24
|
execute_datadog_http_request,
|
|
24
25
|
get_headers,
|
|
25
26
|
MAX_RETRY_COUNT_ON_RATE_LIMIT,
|
|
27
|
+
preprocess_time_fields,
|
|
28
|
+
enhance_error_message,
|
|
29
|
+
fetch_openapi_spec,
|
|
26
30
|
)
|
|
27
31
|
from holmes.plugins.toolsets.utils import toolset_name_for_one_liner
|
|
28
32
|
|
|
29
33
|
# Maximum response size in bytes (10MB)
|
|
30
34
|
MAX_RESPONSE_SIZE = 10 * 1024 * 1024
|
|
31
35
|
|
|
32
|
-
# Whitelisted API endpoint patterns
|
|
36
|
+
# Whitelisted API endpoint patterns with optional hints
|
|
37
|
+
# Format: (pattern, hint) - hint is empty string if no special instructions
|
|
33
38
|
WHITELISTED_ENDPOINTS = [
|
|
34
39
|
# Monitors
|
|
35
|
-
r"^/api/v\d+/monitor(/search)?$",
|
|
36
|
-
r"^/api/v\d+/monitor/\d+(/downtimes)?$",
|
|
37
|
-
r"^/api/v\d+/monitor/groups/search$",
|
|
40
|
+
(r"^/api/v\d+/monitor(/search)?$", ""),
|
|
41
|
+
(r"^/api/v\d+/monitor/\d+(/downtimes)?$", ""),
|
|
42
|
+
(r"^/api/v\d+/monitor/groups/search$", ""),
|
|
38
43
|
# Dashboards
|
|
39
|
-
r"^/api/v\d+/dashboard(/lists)?$",
|
|
40
|
-
r"^/api/v\d+/dashboard/[^/]+$",
|
|
41
|
-
r"^/api/v\d+/dashboard/public/[^/]+$",
|
|
42
|
-
# SLOs
|
|
43
|
-
r"^/api/v\d+/slo(/search)?$",
|
|
44
|
-
r"^/api/v\d+/slo/[^/]+(/history)?$",
|
|
45
|
-
r"^/api/v\d+/slo/[^/]+/corrections$",
|
|
44
|
+
(r"^/api/v\d+/dashboard(/lists)?$", ""),
|
|
45
|
+
(r"^/api/v\d+/dashboard/[^/]+$", ""),
|
|
46
|
+
(r"^/api/v\d+/dashboard/public/[^/]+$", ""),
|
|
47
|
+
# SLOs
|
|
48
|
+
(r"^/api/v\d+/slo(/search)?$", ""),
|
|
49
|
+
(r"^/api/v\d+/slo/[^/]+(/history)?$", ""),
|
|
50
|
+
(r"^/api/v\d+/slo/[^/]+/corrections$", ""),
|
|
46
51
|
# Events
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
(
|
|
53
|
+
r"^/api/v\d+/events$",
|
|
54
|
+
"Use time range parameters 'start' and 'end' as Unix timestamps",
|
|
55
|
+
),
|
|
56
|
+
(r"^/api/v\d+/events/\d+$", ""),
|
|
49
57
|
# Incidents
|
|
50
|
-
r"^/api/v\d+/incidents(/search)?$",
|
|
51
|
-
r"^/api/v\d+/incidents/[^/]+$",
|
|
52
|
-
r"^/api/v\d+/incidents/[^/]+/attachments$",
|
|
53
|
-
r"^/api/v\d+/incidents/[^/]+/connected_integrations$",
|
|
54
|
-
r"^/api/v\d+/incidents/[^/]+/relationships$",
|
|
55
|
-
r"^/api/v\d+/incidents/[^/]+/timeline$",
|
|
58
|
+
(r"^/api/v\d+/incidents(/search)?$", ""),
|
|
59
|
+
(r"^/api/v\d+/incidents/[^/]+$", ""),
|
|
60
|
+
(r"^/api/v\d+/incidents/[^/]+/attachments$", ""),
|
|
61
|
+
(r"^/api/v\d+/incidents/[^/]+/connected_integrations$", ""),
|
|
62
|
+
(r"^/api/v\d+/incidents/[^/]+/relationships$", ""),
|
|
63
|
+
(r"^/api/v\d+/incidents/[^/]+/timeline$", ""),
|
|
56
64
|
# Synthetics
|
|
57
|
-
r"^/api/v\d+/synthetics/tests(/search)?$",
|
|
58
|
-
r"^/api/v\d+/synthetics/tests/[^/]+$",
|
|
59
|
-
r"^/api/v\d+/synthetics/tests/[^/]+/results$",
|
|
60
|
-
r"^/api/v\d+/synthetics/tests/browser/[^/]+/results$",
|
|
61
|
-
r"^/api/v\d+/synthetics/tests/api/[^/]+/results$",
|
|
62
|
-
r"^/api/v\d+/synthetics/locations$",
|
|
63
|
-
# Security
|
|
64
|
-
r"^/api/v\d+/security_monitoring/rules(/search)?$",
|
|
65
|
-
r"^/api/v\d+/security_monitoring/rules/[^/]+$",
|
|
66
|
-
r"^/api/v\d+/security_monitoring/signals(/search)?$",
|
|
67
|
-
r"^/api/v\d+/security_monitoring/signals/[^/]+$",
|
|
68
|
-
#
|
|
69
|
-
r"^/api/v\d+/services$",
|
|
70
|
-
r"^/api/v\d+/services/[^/]+$",
|
|
71
|
-
r"^/api/v\d+/services/[^/]+/dependencies$",
|
|
65
|
+
(r"^/api/v\d+/synthetics/tests(/search)?$", ""),
|
|
66
|
+
(r"^/api/v\d+/synthetics/tests/[^/]+$", ""),
|
|
67
|
+
(r"^/api/v\d+/synthetics/tests/[^/]+/results$", ""),
|
|
68
|
+
(r"^/api/v\d+/synthetics/tests/browser/[^/]+/results$", ""),
|
|
69
|
+
(r"^/api/v\d+/synthetics/tests/api/[^/]+/results$", ""),
|
|
70
|
+
(r"^/api/v\d+/synthetics/locations$", ""),
|
|
71
|
+
# Security
|
|
72
|
+
(r"^/api/v\d+/security_monitoring/rules(/search)?$", ""),
|
|
73
|
+
(r"^/api/v\d+/security_monitoring/rules/[^/]+$", ""),
|
|
74
|
+
(r"^/api/v\d+/security_monitoring/signals(/search)?$", ""),
|
|
75
|
+
(r"^/api/v\d+/security_monitoring/signals/[^/]+$", ""),
|
|
76
|
+
# Services
|
|
77
|
+
(r"^/api/v\d+/services$", ""),
|
|
78
|
+
(r"^/api/v\d+/services/[^/]+$", ""),
|
|
79
|
+
(r"^/api/v\d+/services/[^/]+/dependencies$", ""),
|
|
80
|
+
(r"^/api/v\d+/service_dependencies$", ""),
|
|
72
81
|
# Hosts
|
|
73
|
-
r"^/api/v\d+/hosts$",
|
|
74
|
-
r"^/api/v\d+/hosts/totals$",
|
|
75
|
-
r"^/api/v\d+/hosts/[^/]+$",
|
|
76
|
-
# Usage
|
|
77
|
-
r"^/api/v\d+/usage/[^/]+$",
|
|
78
|
-
r"^/api/v\d+/usage/summary$",
|
|
79
|
-
r"^/api/v\d+/usage/billable-summary$",
|
|
80
|
-
r"^/api/v\d+/usage/cost_by_org$",
|
|
81
|
-
r"^/api/v\d+/usage/estimated_cost$",
|
|
82
|
+
(r"^/api/v\d+/hosts$", ""),
|
|
83
|
+
(r"^/api/v\d+/hosts/totals$", ""),
|
|
84
|
+
(r"^/api/v\d+/hosts/[^/]+$", ""),
|
|
85
|
+
# Usage
|
|
86
|
+
(r"^/api/v\d+/usage/[^/]+$", ""),
|
|
87
|
+
(r"^/api/v\d+/usage/summary$", ""),
|
|
88
|
+
(r"^/api/v\d+/usage/billable-summary$", ""),
|
|
89
|
+
(r"^/api/v\d+/usage/cost_by_org$", ""),
|
|
90
|
+
(r"^/api/v\d+/usage/estimated_cost$", ""),
|
|
82
91
|
# Processes
|
|
83
|
-
r"^/api/v\d+/processes$",
|
|
92
|
+
(r"^/api/v\d+/processes$", ""),
|
|
84
93
|
# Tags
|
|
85
|
-
r"^/api/v\d+/tags/hosts(/[^/]+)?$",
|
|
94
|
+
(r"^/api/v\d+/tags/hosts(/[^/]+)?$", ""),
|
|
86
95
|
# Notebooks
|
|
87
|
-
r"^/api/v\d+/notebooks$",
|
|
88
|
-
r"^/api/v\d+/notebooks/\d+$",
|
|
89
|
-
# Service Dependencies
|
|
90
|
-
r"^/api/v\d+/service_dependencies$",
|
|
96
|
+
(r"^/api/v\d+/notebooks$", ""),
|
|
97
|
+
(r"^/api/v\d+/notebooks/\d+$", ""),
|
|
91
98
|
# Organization
|
|
92
|
-
r"^/api/v\d+/org$",
|
|
93
|
-
r"^/api/v\d+/org/[^/]+$",
|
|
94
|
-
# Users
|
|
95
|
-
r"^/api/v\d+/users$",
|
|
96
|
-
r"^/api/v\d+/users/[^/]+$",
|
|
97
|
-
# Teams
|
|
98
|
-
r"^/api/v\d+/teams$",
|
|
99
|
-
r"^/api/v\d+/teams/[^/]+$",
|
|
100
|
-
#
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
99
|
+
(r"^/api/v\d+/org$", ""),
|
|
100
|
+
(r"^/api/v\d+/org/[^/]+$", ""),
|
|
101
|
+
# Users
|
|
102
|
+
(r"^/api/v\d+/users$", ""),
|
|
103
|
+
(r"^/api/v\d+/users/[^/]+$", ""),
|
|
104
|
+
# Teams
|
|
105
|
+
(r"^/api/v\d+/teams$", ""),
|
|
106
|
+
(r"^/api/v\d+/teams/[^/]+$", ""),
|
|
107
|
+
# Logs
|
|
108
|
+
(
|
|
109
|
+
r"^/api/v1/logs/config/indexes$",
|
|
110
|
+
"When available, prefer using fetch_pod_logs tool from datadog/logs toolset instead of calling this API directly with the datadog/general toolset",
|
|
111
|
+
),
|
|
112
|
+
(
|
|
113
|
+
r"^/api/v2/logs/events$",
|
|
114
|
+
"When available, prefer using fetch_pod_logs tool from datadog/logs toolset instead of calling this API directly with the datadog/general toolset. Use RFC3339 timestamps (e.g., '2024-01-01T00:00:00Z')",
|
|
115
|
+
),
|
|
116
|
+
(
|
|
117
|
+
r"^/api/v2/logs/events/search$",
|
|
118
|
+
'When available, prefer using fetch_pod_logs tool from datadog/logs toolset instead of calling this API directly with the datadog/general toolset. RFC3339 time format. Example: {"filter": {"from": "2024-01-01T00:00:00Z", "to": "2024-01-02T00:00:00Z", "query": "*"}}',
|
|
119
|
+
),
|
|
120
|
+
(
|
|
121
|
+
r"^/api/v2/logs/analytics/aggregate$",
|
|
122
|
+
"When available, prefer using fetch_pod_logs tool from datadog/logs toolset instead of calling this API directly with the datadog/general toolset. Do not include 'sort' parameter",
|
|
123
|
+
),
|
|
124
|
+
# Metrics
|
|
125
|
+
(
|
|
126
|
+
r"^/api/v\d+/metrics$",
|
|
127
|
+
"When available, prefer using query_datadog_metrics tool from datadog/metrics toolset instead of calling this API directly with the datadog/general toolset",
|
|
128
|
+
),
|
|
129
|
+
(
|
|
130
|
+
r"^/api/v\d+/metrics/[^/]+$",
|
|
131
|
+
"When available, prefer using get_datadog_metric_metadata tool from datadog/metrics toolset instead of calling this API directly with the datadog/general toolset",
|
|
132
|
+
),
|
|
133
|
+
(
|
|
134
|
+
r"^/api/v\d+/query$",
|
|
135
|
+
"When available, prefer using query_datadog_metrics tool from datadog/metrics toolset instead of calling this API directly with the datadog/general toolset. Use 'from' and 'to' as Unix timestamps",
|
|
136
|
+
),
|
|
137
|
+
(
|
|
138
|
+
r"^/api/v\d+/search/query$",
|
|
139
|
+
"When available, prefer using query_datadog_metrics tool from datadog/metrics toolset instead of calling this API directly with the datadog/general toolset",
|
|
140
|
+
),
|
|
105
141
|
]
|
|
106
142
|
|
|
107
143
|
# Blacklisted path segments that indicate write operations
|
|
@@ -146,9 +182,13 @@ WHITELISTED_POST_ENDPOINTS = [
|
|
|
146
182
|
r"^/api/v\d+/security_monitoring/rules/search$",
|
|
147
183
|
r"^/api/v\d+/security_monitoring/signals/search$",
|
|
148
184
|
r"^/api/v\d+/logs/events/search$",
|
|
185
|
+
r"^/api/v2/logs/events/search$",
|
|
186
|
+
r"^/api/v2/logs/analytics/aggregate$",
|
|
149
187
|
r"^/api/v\d+/spans/events/search$",
|
|
150
188
|
r"^/api/v\d+/rum/events/search$",
|
|
151
189
|
r"^/api/v\d+/audit/events/search$",
|
|
190
|
+
r"^/api/v\d+/query$",
|
|
191
|
+
r"^/api/v\d+/search/query$",
|
|
152
192
|
]
|
|
153
193
|
|
|
154
194
|
|
|
@@ -165,11 +205,12 @@ class DatadogGeneralToolset(Toolset):
|
|
|
165
205
|
"""General-purpose Datadog API toolset for read-only operations not covered by specialized toolsets."""
|
|
166
206
|
|
|
167
207
|
dd_config: Optional[DatadogGeneralConfig] = None
|
|
208
|
+
openapi_spec: Optional[Dict[str, Any]] = None
|
|
168
209
|
|
|
169
210
|
def __init__(self):
|
|
170
211
|
super().__init__(
|
|
171
212
|
name="datadog/general",
|
|
172
|
-
description="General-purpose Datadog API access for read-only operations including monitors, dashboards, SLOs, incidents, synthetics, and more",
|
|
213
|
+
description="General-purpose Datadog API access for read-only operations including monitors, dashboards, SLOs, incidents, synthetics, logs, metrics, and more. Note: For logs and metrics, prefer using the specialized datadog/logs and datadog/metrics toolsets when available as they provide optimized functionality",
|
|
173
214
|
docs_url="https://holmesgpt.dev/data-sources/builtin-toolsets/datadog/",
|
|
174
215
|
icon_url="https://imgix.datadoghq.com//img/about/presskit/DDlogo.jpg",
|
|
175
216
|
prerequisites=[CallablePrerequisite(callable=self.prerequisites_callable)],
|
|
@@ -190,11 +231,27 @@ class DatadogGeneralToolset(Toolset):
|
|
|
190
231
|
def prerequisites_callable(self, config: dict[str, Any]) -> Tuple[bool, str]:
|
|
191
232
|
"""Check prerequisites with configuration."""
|
|
192
233
|
if not config:
|
|
193
|
-
return
|
|
234
|
+
return (
|
|
235
|
+
False,
|
|
236
|
+
"Missing config for dd_api_key, dd_app_key, or site_api_url. For details: https://holmesgpt.dev/data-sources/builtin-toolsets/datadog/",
|
|
237
|
+
)
|
|
194
238
|
|
|
195
239
|
try:
|
|
196
240
|
dd_config = DatadogGeneralConfig(**config)
|
|
197
241
|
self.dd_config = dd_config
|
|
242
|
+
|
|
243
|
+
# Fetch OpenAPI spec on startup for better error messages and documentation
|
|
244
|
+
logging.debug("Fetching Datadog OpenAPI specification...")
|
|
245
|
+
self.openapi_spec = fetch_openapi_spec(version="both")
|
|
246
|
+
if self.openapi_spec:
|
|
247
|
+
logging.info(
|
|
248
|
+
f"Successfully loaded OpenAPI spec with {len(self.openapi_spec.get('paths', {}))} endpoints"
|
|
249
|
+
)
|
|
250
|
+
else:
|
|
251
|
+
logging.warning(
|
|
252
|
+
"Could not fetch OpenAPI spec; enhanced error messages will be limited"
|
|
253
|
+
)
|
|
254
|
+
|
|
198
255
|
success, error_msg = self._perform_healthcheck(dd_config)
|
|
199
256
|
return success, error_msg
|
|
200
257
|
except Exception as e:
|
|
@@ -205,7 +262,8 @@ class DatadogGeneralToolset(Toolset):
|
|
|
205
262
|
"""Perform health check on Datadog API."""
|
|
206
263
|
try:
|
|
207
264
|
logging.info("Performing Datadog general API configuration healthcheck...")
|
|
208
|
-
|
|
265
|
+
base_url = str(dd_config.site_api_url).rstrip("/")
|
|
266
|
+
url = f"{base_url}/api/v1/validate"
|
|
209
267
|
headers = get_headers(dd_config)
|
|
210
268
|
|
|
211
269
|
data = execute_datadog_http_request(
|
|
@@ -217,7 +275,7 @@ class DatadogGeneralToolset(Toolset):
|
|
|
217
275
|
)
|
|
218
276
|
|
|
219
277
|
if data.get("valid", False):
|
|
220
|
-
logging.
|
|
278
|
+
logging.debug("Datadog general API healthcheck completed successfully")
|
|
221
279
|
return True, ""
|
|
222
280
|
else:
|
|
223
281
|
error_msg = "Datadog API key validation failed"
|
|
@@ -266,7 +324,7 @@ def is_endpoint_allowed(
|
|
|
266
324
|
return False, f"POST method not allowed for endpoint: {path}"
|
|
267
325
|
|
|
268
326
|
elif method == "GET":
|
|
269
|
-
for pattern in WHITELISTED_ENDPOINTS:
|
|
327
|
+
for pattern, _ in WHITELISTED_ENDPOINTS:
|
|
270
328
|
if re.match(pattern, path):
|
|
271
329
|
return True, ""
|
|
272
330
|
|
|
@@ -280,6 +338,23 @@ def is_endpoint_allowed(
|
|
|
280
338
|
return False, f"HTTP method {method} not allowed for {path}"
|
|
281
339
|
|
|
282
340
|
|
|
341
|
+
def get_endpoint_hint(endpoint: str) -> str:
|
|
342
|
+
"""
|
|
343
|
+
Get hint for an endpoint if available.
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
Hint string or empty string if no hint
|
|
347
|
+
"""
|
|
348
|
+
parsed = urlparse(endpoint)
|
|
349
|
+
path = parsed.path
|
|
350
|
+
|
|
351
|
+
for pattern, hint in WHITELISTED_ENDPOINTS:
|
|
352
|
+
if re.match(pattern, path):
|
|
353
|
+
return hint
|
|
354
|
+
|
|
355
|
+
return ""
|
|
356
|
+
|
|
357
|
+
|
|
283
358
|
class BaseDatadogGeneralTool(Tool):
|
|
284
359
|
"""Base class for general Datadog API tools."""
|
|
285
360
|
|
|
@@ -292,7 +367,7 @@ class DatadogAPIGet(BaseDatadogGeneralTool):
|
|
|
292
367
|
def __init__(self, toolset: "DatadogGeneralToolset"):
|
|
293
368
|
super().__init__(
|
|
294
369
|
name="datadog_api_get",
|
|
295
|
-
description="Make a GET request to a Datadog API endpoint for read-only operations",
|
|
370
|
+
description="[datadog/general toolset] Make a GET request to a Datadog API endpoint for read-only operations",
|
|
296
371
|
parameters={
|
|
297
372
|
"endpoint": ToolParameter(
|
|
298
373
|
description="The API endpoint path (e.g., '/api/v1/monitors', '/api/v2/events')",
|
|
@@ -300,7 +375,14 @@ class DatadogAPIGet(BaseDatadogGeneralTool):
|
|
|
300
375
|
required=True,
|
|
301
376
|
),
|
|
302
377
|
"query_params": ToolParameter(
|
|
303
|
-
description="Query parameters as a dictionary
|
|
378
|
+
description="""Query parameters as a dictionary.
|
|
379
|
+
Time format requirements:
|
|
380
|
+
- v1 API: Unix timestamps in seconds (e.g., {'start': 1704067200, 'end': 1704153600})
|
|
381
|
+
- v2 API: RFC3339 format (e.g., {'from': '2024-01-01T00:00:00Z', 'to': '2024-01-02T00:00:00Z'})
|
|
382
|
+
- Relative times like '-24h', 'now', '-7d' will be auto-converted to proper format
|
|
383
|
+
|
|
384
|
+
Example for events: {'start': 1704067200, 'end': 1704153600}
|
|
385
|
+
Example for monitors: {'name': 'my-monitor', 'tags': 'env:prod'}""",
|
|
304
386
|
type="object",
|
|
305
387
|
required=False,
|
|
306
388
|
),
|
|
@@ -318,9 +400,7 @@ class DatadogAPIGet(BaseDatadogGeneralTool):
|
|
|
318
400
|
description = params.get("description", "API call")
|
|
319
401
|
return f"{toolset_name_for_one_liner(self.toolset.name)}: {description}"
|
|
320
402
|
|
|
321
|
-
def _invoke(
|
|
322
|
-
self, params: dict, user_approved: bool = False
|
|
323
|
-
) -> StructuredToolResult:
|
|
403
|
+
def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
|
|
324
404
|
"""Execute the GET request."""
|
|
325
405
|
logging.info("=" * 60)
|
|
326
406
|
logging.info("DatadogAPIGet Tool Invocation:")
|
|
@@ -365,11 +445,14 @@ class DatadogAPIGet(BaseDatadogGeneralTool):
|
|
|
365
445
|
|
|
366
446
|
logging.info(f"Full API URL: {url}")
|
|
367
447
|
|
|
448
|
+
# Preprocess time fields if any
|
|
449
|
+
processed_params = preprocess_time_fields(query_params, endpoint)
|
|
450
|
+
|
|
368
451
|
# Execute request
|
|
369
452
|
response = execute_datadog_http_request(
|
|
370
453
|
url=url,
|
|
371
454
|
headers=headers,
|
|
372
|
-
payload_or_params=
|
|
455
|
+
payload_or_params=processed_params,
|
|
373
456
|
timeout=self.toolset.dd_config.request_timeout,
|
|
374
457
|
method="GET",
|
|
375
458
|
)
|
|
@@ -403,6 +486,11 @@ class DatadogAPIGet(BaseDatadogGeneralTool):
|
|
|
403
486
|
)
|
|
404
487
|
elif e.status_code == 404:
|
|
405
488
|
error_msg = f"Endpoint not found: {endpoint}"
|
|
489
|
+
elif e.status_code == 400:
|
|
490
|
+
# Use enhanced error message for 400 errors
|
|
491
|
+
error_msg = enhance_error_message(
|
|
492
|
+
e, endpoint, "GET", str(self.toolset.dd_config.site_api_url)
|
|
493
|
+
)
|
|
406
494
|
else:
|
|
407
495
|
error_msg = f"API error {e.status_code}: {str(e)}"
|
|
408
496
|
|
|
@@ -430,7 +518,7 @@ class DatadogAPIPostSearch(BaseDatadogGeneralTool):
|
|
|
430
518
|
def __init__(self, toolset: "DatadogGeneralToolset"):
|
|
431
519
|
super().__init__(
|
|
432
520
|
name="datadog_api_post_search",
|
|
433
|
-
description="Make a POST request to Datadog search/query endpoints for complex filtering",
|
|
521
|
+
description="[datadog/general toolset] Make a POST request to Datadog search/query endpoints for complex filtering",
|
|
434
522
|
parameters={
|
|
435
523
|
"endpoint": ToolParameter(
|
|
436
524
|
description="The search API endpoint (e.g., '/api/v2/monitor/search', '/api/v2/events/search')",
|
|
@@ -438,7 +526,29 @@ class DatadogAPIPostSearch(BaseDatadogGeneralTool):
|
|
|
438
526
|
required=True,
|
|
439
527
|
),
|
|
440
528
|
"body": ToolParameter(
|
|
441
|
-
description="Request body for the search/filter operation
|
|
529
|
+
description="""Request body for the search/filter operation.
|
|
530
|
+
Time format requirements:
|
|
531
|
+
- v1 API: Unix timestamps (e.g., 1704067200)
|
|
532
|
+
- v2 API: RFC3339 format (e.g., '2024-01-01T00:00:00Z')
|
|
533
|
+
- Relative times like '-24h', 'now', '-7d' will be auto-converted
|
|
534
|
+
|
|
535
|
+
Example for logs search:
|
|
536
|
+
{
|
|
537
|
+
"filter": {
|
|
538
|
+
"from": "2024-01-01T00:00:00Z",
|
|
539
|
+
"to": "2024-01-02T00:00:00Z",
|
|
540
|
+
"query": "*"
|
|
541
|
+
},
|
|
542
|
+
"sort": "-timestamp",
|
|
543
|
+
"page": {"limit": 50}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
Example for monitor search:
|
|
547
|
+
{
|
|
548
|
+
"query": "env:production",
|
|
549
|
+
"page": 0,
|
|
550
|
+
"per_page": 20
|
|
551
|
+
}""",
|
|
442
552
|
type="object",
|
|
443
553
|
required=True,
|
|
444
554
|
),
|
|
@@ -456,9 +566,7 @@ class DatadogAPIPostSearch(BaseDatadogGeneralTool):
|
|
|
456
566
|
description = params.get("description", "Search")
|
|
457
567
|
return f"{toolset_name_for_one_liner(self.toolset.name)}: {description}"
|
|
458
568
|
|
|
459
|
-
def _invoke(
|
|
460
|
-
self, params: dict, user_approved: bool = False
|
|
461
|
-
) -> StructuredToolResult:
|
|
569
|
+
def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
|
|
462
570
|
"""Execute the POST search request."""
|
|
463
571
|
logging.info("=" * 60)
|
|
464
572
|
logging.info("DatadogAPIPostSearch Tool Invocation:")
|
|
@@ -501,11 +609,14 @@ class DatadogAPIPostSearch(BaseDatadogGeneralTool):
|
|
|
501
609
|
|
|
502
610
|
logging.info(f"Full API URL: {url}")
|
|
503
611
|
|
|
612
|
+
# Preprocess time fields if any
|
|
613
|
+
processed_body = preprocess_time_fields(body, endpoint)
|
|
614
|
+
|
|
504
615
|
# Execute request
|
|
505
616
|
response = execute_datadog_http_request(
|
|
506
617
|
url=url,
|
|
507
618
|
headers=headers,
|
|
508
|
-
payload_or_params=
|
|
619
|
+
payload_or_params=processed_body,
|
|
509
620
|
timeout=self.toolset.dd_config.request_timeout,
|
|
510
621
|
method="POST",
|
|
511
622
|
)
|
|
@@ -539,6 +650,11 @@ class DatadogAPIPostSearch(BaseDatadogGeneralTool):
|
|
|
539
650
|
)
|
|
540
651
|
elif e.status_code == 404:
|
|
541
652
|
error_msg = f"Endpoint not found: {endpoint}"
|
|
653
|
+
elif e.status_code == 400:
|
|
654
|
+
# Use enhanced error message for 400 errors
|
|
655
|
+
error_msg = enhance_error_message(
|
|
656
|
+
e, endpoint, "POST", str(self.toolset.dd_config.site_api_url)
|
|
657
|
+
)
|
|
542
658
|
else:
|
|
543
659
|
error_msg = f"API error {e.status_code}: {str(e)}"
|
|
544
660
|
|
|
@@ -564,10 +680,10 @@ class ListDatadogAPIResources(BaseDatadogGeneralTool):
|
|
|
564
680
|
def __init__(self, toolset: "DatadogGeneralToolset"):
|
|
565
681
|
super().__init__(
|
|
566
682
|
name="list_datadog_api_resources",
|
|
567
|
-
description="List available Datadog API resources and endpoints that can be accessed",
|
|
683
|
+
description="[datadog/general toolset] List available Datadog API resources and endpoints that can be accessed",
|
|
568
684
|
parameters={
|
|
569
|
-
"
|
|
570
|
-
description="
|
|
685
|
+
"search_regex": ToolParameter(
|
|
686
|
+
description="Optional regex pattern to filter endpoints (e.g., 'monitor', 'logs|metrics', 'security.*signals', 'v2/.*search$'). If not provided, shows all endpoints.",
|
|
571
687
|
type="string",
|
|
572
688
|
required=False,
|
|
573
689
|
),
|
|
@@ -577,142 +693,160 @@ class ListDatadogAPIResources(BaseDatadogGeneralTool):
|
|
|
577
693
|
|
|
578
694
|
def get_parameterized_one_liner(self, params: dict) -> str:
|
|
579
695
|
"""Get a one-liner description of the tool invocation."""
|
|
580
|
-
|
|
581
|
-
return f"{toolset_name_for_one_liner(self.toolset.name)}: List API Resources ({
|
|
696
|
+
search = params.get("search_regex", "all")
|
|
697
|
+
return f"{toolset_name_for_one_liner(self.toolset.name)}: List API Resources (search: {search})"
|
|
582
698
|
|
|
583
|
-
def _invoke(
|
|
584
|
-
self, params: dict, user_approved: bool = False
|
|
585
|
-
) -> StructuredToolResult:
|
|
699
|
+
def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
|
|
586
700
|
"""List available API resources."""
|
|
587
|
-
|
|
701
|
+
search_regex = params.get("search_regex", "")
|
|
588
702
|
|
|
589
703
|
logging.info("=" * 60)
|
|
590
704
|
logging.info("ListDatadogAPIResources Tool Invocation:")
|
|
591
|
-
logging.info(f"
|
|
705
|
+
logging.info(f" Search regex: {search_regex or 'None (showing all)'}")
|
|
706
|
+
logging.info(f" OpenAPI Spec Loaded: {self.toolset.openapi_spec is not None}")
|
|
592
707
|
logging.info("=" * 60)
|
|
593
708
|
|
|
594
|
-
#
|
|
595
|
-
|
|
596
|
-
"monitors": {
|
|
597
|
-
"description": "Monitor management and alerting",
|
|
598
|
-
"endpoints": [
|
|
599
|
-
"GET /api/v1/monitor - List all monitors",
|
|
600
|
-
"GET /api/v1/monitor/{id} - Get a monitor by ID",
|
|
601
|
-
"POST /api/v1/monitor/search - Search monitors",
|
|
602
|
-
"GET /api/v1/monitor/groups/search - Search monitor groups",
|
|
603
|
-
],
|
|
604
|
-
},
|
|
605
|
-
"dashboards": {
|
|
606
|
-
"description": "Dashboard and visualization management",
|
|
607
|
-
"endpoints": [
|
|
608
|
-
"GET /api/v1/dashboard - List all dashboards",
|
|
609
|
-
"GET /api/v1/dashboard/{id} - Get a dashboard by ID",
|
|
610
|
-
"POST /api/v1/dashboard/lists - List dashboard lists",
|
|
611
|
-
"GET /api/v1/dashboard/public/{token} - Get public dashboard",
|
|
612
|
-
],
|
|
613
|
-
},
|
|
614
|
-
"slos": {
|
|
615
|
-
"description": "Service Level Objectives",
|
|
616
|
-
"endpoints": [
|
|
617
|
-
"GET /api/v1/slo - List all SLOs",
|
|
618
|
-
"GET /api/v1/slo/{id} - Get an SLO by ID",
|
|
619
|
-
"GET /api/v1/slo/{id}/history - Get SLO history",
|
|
620
|
-
"POST /api/v1/slo/search - Search SLOs",
|
|
621
|
-
"GET /api/v1/slo/{id}/corrections - Get SLO corrections",
|
|
622
|
-
],
|
|
623
|
-
},
|
|
624
|
-
"incidents": {
|
|
625
|
-
"description": "Incident management",
|
|
626
|
-
"endpoints": [
|
|
627
|
-
"GET /api/v2/incidents - List incidents",
|
|
628
|
-
"GET /api/v2/incidents/{id} - Get incident details",
|
|
629
|
-
"POST /api/v2/incidents/search - Search incidents",
|
|
630
|
-
"GET /api/v2/incidents/{id}/timeline - Get incident timeline",
|
|
631
|
-
"GET /api/v2/incidents/{id}/attachments - Get incident attachments",
|
|
632
|
-
],
|
|
633
|
-
},
|
|
634
|
-
"synthetics": {
|
|
635
|
-
"description": "Synthetic monitoring and testing",
|
|
636
|
-
"endpoints": [
|
|
637
|
-
"GET /api/v1/synthetics/tests - List synthetic tests",
|
|
638
|
-
"GET /api/v1/synthetics/tests/{id} - Get test details",
|
|
639
|
-
"POST /api/v1/synthetics/tests/search - Search tests",
|
|
640
|
-
"GET /api/v1/synthetics/tests/{id}/results - Get test results",
|
|
641
|
-
"GET /api/v1/synthetics/locations - List test locations",
|
|
642
|
-
],
|
|
643
|
-
},
|
|
644
|
-
"security": {
|
|
645
|
-
"description": "Security monitoring and detection",
|
|
646
|
-
"endpoints": [
|
|
647
|
-
"GET /api/v2/security_monitoring/rules - List security rules",
|
|
648
|
-
"GET /api/v2/security_monitoring/rules/{id} - Get rule details",
|
|
649
|
-
"POST /api/v2/security_monitoring/rules/search - Search rules",
|
|
650
|
-
"POST /api/v2/security_monitoring/signals/search - Search security signals",
|
|
651
|
-
],
|
|
652
|
-
},
|
|
653
|
-
"hosts": {
|
|
654
|
-
"description": "Host and infrastructure monitoring",
|
|
655
|
-
"endpoints": [
|
|
656
|
-
"GET /api/v1/hosts - List all hosts",
|
|
657
|
-
"GET /api/v1/hosts/{name} - Get host details",
|
|
658
|
-
"GET /api/v1/hosts/totals - Get host totals",
|
|
659
|
-
"GET /api/v1/tags/hosts - Get host tags",
|
|
660
|
-
],
|
|
661
|
-
},
|
|
662
|
-
"events": {
|
|
663
|
-
"description": "Event stream and management",
|
|
664
|
-
"endpoints": [
|
|
665
|
-
"GET /api/v1/events - Query event stream",
|
|
666
|
-
"GET /api/v1/events/{id} - Get event details",
|
|
667
|
-
"POST /api/v2/events/search - Search events",
|
|
668
|
-
],
|
|
669
|
-
},
|
|
670
|
-
"usage": {
|
|
671
|
-
"description": "Usage and billing information",
|
|
672
|
-
"endpoints": [
|
|
673
|
-
"GET /api/v1/usage/summary - Get usage summary",
|
|
674
|
-
"GET /api/v1/usage/billable-summary - Get billable summary",
|
|
675
|
-
"GET /api/v1/usage/estimated_cost - Get estimated costs",
|
|
676
|
-
"GET /api/v2/usage/cost_by_org - Get costs by organization",
|
|
677
|
-
],
|
|
678
|
-
},
|
|
679
|
-
"services": {
|
|
680
|
-
"description": "APM service information",
|
|
681
|
-
"endpoints": [
|
|
682
|
-
"GET /api/v2/services - List services",
|
|
683
|
-
"GET /api/v2/services/{service} - Get service details",
|
|
684
|
-
"GET /api/v2/services/{service}/dependencies - Get service dependencies",
|
|
685
|
-
],
|
|
686
|
-
},
|
|
687
|
-
}
|
|
709
|
+
# Filter endpoints based on regex search
|
|
710
|
+
matching_endpoints = []
|
|
688
711
|
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
712
|
+
if search_regex:
|
|
713
|
+
try:
|
|
714
|
+
search_pattern = re.compile(search_regex, re.IGNORECASE)
|
|
715
|
+
except re.error as e:
|
|
693
716
|
return StructuredToolResult(
|
|
694
717
|
status=StructuredToolResultStatus.ERROR,
|
|
695
|
-
error=f"
|
|
718
|
+
error=f"Invalid regex pattern: {e}",
|
|
696
719
|
params=params,
|
|
697
720
|
)
|
|
698
|
-
|
|
721
|
+
else:
|
|
722
|
+
search_pattern = None
|
|
723
|
+
|
|
724
|
+
# Build list of matching endpoints
|
|
725
|
+
for pattern, hint in WHITELISTED_ENDPOINTS:
|
|
726
|
+
# Create a readable endpoint example from the pattern
|
|
727
|
+
example_endpoint = pattern.replace(r"^/api/v\d+", "/api/v1")
|
|
728
|
+
example_endpoint = example_endpoint.replace(r"(/search)?$", "")
|
|
729
|
+
example_endpoint = example_endpoint.replace(r"(/[^/]+)?$", "/{id}")
|
|
730
|
+
example_endpoint = example_endpoint.replace(r"/[^/]+$", "/{id}")
|
|
731
|
+
example_endpoint = example_endpoint.replace(r"/\d+$", "/{id}")
|
|
732
|
+
example_endpoint = example_endpoint.replace("$", "")
|
|
733
|
+
example_endpoint = example_endpoint.replace("^", "")
|
|
734
|
+
|
|
735
|
+
# Apply search filter if provided
|
|
736
|
+
if search_pattern and not search_pattern.search(example_endpoint):
|
|
737
|
+
continue
|
|
738
|
+
|
|
739
|
+
# Determine HTTP methods
|
|
740
|
+
if "search" in pattern or "query" in pattern or "aggregate" in pattern:
|
|
741
|
+
methods = "POST"
|
|
742
|
+
elif "/search)?$" in pattern:
|
|
743
|
+
methods = "GET/POST"
|
|
744
|
+
else:
|
|
745
|
+
methods = "GET"
|
|
699
746
|
|
|
700
|
-
|
|
701
|
-
|
|
747
|
+
endpoint_info = {
|
|
748
|
+
"endpoint": example_endpoint,
|
|
749
|
+
"methods": methods,
|
|
750
|
+
"hint": hint,
|
|
751
|
+
"pattern": pattern,
|
|
752
|
+
}
|
|
753
|
+
matching_endpoints.append(endpoint_info)
|
|
702
754
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
output.append(f" • {endpoint}")
|
|
710
|
-
output.append("")
|
|
755
|
+
if not matching_endpoints:
|
|
756
|
+
return StructuredToolResult(
|
|
757
|
+
status=StructuredToolResultStatus.SUCCESS,
|
|
758
|
+
data=f"No endpoints found matching regex: {search_regex}",
|
|
759
|
+
params=params,
|
|
760
|
+
)
|
|
711
761
|
|
|
762
|
+
# Format output
|
|
763
|
+
output = ["Available Datadog API Endpoints", "=" * 40]
|
|
764
|
+
|
|
765
|
+
if search_regex:
|
|
766
|
+
output.append(f"Filter: {search_regex}")
|
|
767
|
+
output.append(f"Found: {len(matching_endpoints)} endpoints")
|
|
768
|
+
output.append("")
|
|
769
|
+
|
|
770
|
+
# List endpoints with spec info if available
|
|
771
|
+
for info in matching_endpoints:
|
|
772
|
+
line = f"{info['methods']:8} {info['endpoint']}"
|
|
773
|
+
if info["hint"]:
|
|
774
|
+
line += f"\n {info['hint']}"
|
|
775
|
+
|
|
776
|
+
# Add OpenAPI spec info for this specific endpoint if available
|
|
777
|
+
if self.toolset.openapi_spec and "paths" in self.toolset.openapi_spec:
|
|
778
|
+
# Try to find matching path in OpenAPI spec
|
|
779
|
+
spec_path = None
|
|
780
|
+
for path in self.toolset.openapi_spec["paths"].keys():
|
|
781
|
+
if re.match(info["pattern"], path):
|
|
782
|
+
spec_path = path
|
|
783
|
+
break
|
|
784
|
+
|
|
785
|
+
if spec_path and spec_path in self.toolset.openapi_spec["paths"]:
|
|
786
|
+
path_spec = self.toolset.openapi_spec["paths"][spec_path]
|
|
787
|
+
# Add actual OpenAPI schema for the endpoint
|
|
788
|
+
for method in ["get", "post", "put", "delete"]:
|
|
789
|
+
if method in path_spec:
|
|
790
|
+
method_spec = path_spec[method]
|
|
791
|
+
line += f"\n\n OpenAPI Schema ({method.upper()}):"
|
|
792
|
+
|
|
793
|
+
# Add summary if available
|
|
794
|
+
if "summary" in method_spec:
|
|
795
|
+
line += f"\n Summary: {method_spec['summary']}"
|
|
796
|
+
|
|
797
|
+
# Add parameters if available
|
|
798
|
+
if "parameters" in method_spec:
|
|
799
|
+
line += "\n Parameters:"
|
|
800
|
+
for param in method_spec["parameters"]:
|
|
801
|
+
param_info = f"\n - {param.get('name', 'unknown')} ({param.get('in', 'unknown')})"
|
|
802
|
+
if param.get("required", False):
|
|
803
|
+
param_info += " [required]"
|
|
804
|
+
if "description" in param:
|
|
805
|
+
param_info += f": {param['description'][:100]}"
|
|
806
|
+
line += param_info
|
|
807
|
+
|
|
808
|
+
# Add request body schema if available
|
|
809
|
+
if "requestBody" in method_spec:
|
|
810
|
+
line += "\n Request Body:"
|
|
811
|
+
if "content" in method_spec["requestBody"]:
|
|
812
|
+
for content_type, content_spec in method_spec[
|
|
813
|
+
"requestBody"
|
|
814
|
+
]["content"].items():
|
|
815
|
+
if "schema" in content_spec:
|
|
816
|
+
# Show a compact version of the schema
|
|
817
|
+
schema_str = json.dumps(
|
|
818
|
+
content_spec["schema"], indent=10
|
|
819
|
+
)[:500]
|
|
820
|
+
if (
|
|
821
|
+
len(json.dumps(content_spec["schema"]))
|
|
822
|
+
> 500
|
|
823
|
+
):
|
|
824
|
+
schema_str += "..."
|
|
825
|
+
line += f"\n Content-Type: {content_type}"
|
|
826
|
+
line += f"\n Schema: {schema_str}"
|
|
827
|
+
|
|
828
|
+
# Add response schema sample if available
|
|
829
|
+
if "responses" in method_spec:
|
|
830
|
+
if "200" in method_spec["responses"]:
|
|
831
|
+
line += "\n Response (200):"
|
|
832
|
+
resp = method_spec["responses"]["200"]
|
|
833
|
+
if "description" in resp:
|
|
834
|
+
line += f"\n {resp['description']}"
|
|
835
|
+
break
|
|
836
|
+
|
|
837
|
+
output.append(line)
|
|
838
|
+
|
|
839
|
+
output.append("")
|
|
712
840
|
output.append(
|
|
713
841
|
"Note: All endpoints are read-only. Use the appropriate tool with the endpoint path."
|
|
714
842
|
)
|
|
715
843
|
output.append("Example: datadog_api_get with endpoint='/api/v1/monitors'")
|
|
844
|
+
output.append("")
|
|
845
|
+
output.append("Search examples:")
|
|
846
|
+
output.append(" • 'monitor' - find all monitor endpoints")
|
|
847
|
+
output.append(" • 'logs|metrics' - find logs OR metrics endpoints")
|
|
848
|
+
output.append(" • 'v2.*search$' - find all v2 search endpoints")
|
|
849
|
+
output.append(" • 'security.*signals' - find security signals endpoints")
|
|
716
850
|
|
|
717
851
|
return StructuredToolResult(
|
|
718
852
|
status=StructuredToolResultStatus.SUCCESS,
|