holmesgpt 0.11.5__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.

Files changed (183) hide show
  1. holmes/.git_archival.json +7 -0
  2. holmes/__init__.py +76 -0
  3. holmes/__init__.py.bak +76 -0
  4. holmes/clients/robusta_client.py +24 -0
  5. holmes/common/env_vars.py +47 -0
  6. holmes/config.py +526 -0
  7. holmes/core/__init__.py +0 -0
  8. holmes/core/conversations.py +578 -0
  9. holmes/core/investigation.py +152 -0
  10. holmes/core/investigation_structured_output.py +264 -0
  11. holmes/core/issue.py +54 -0
  12. holmes/core/llm.py +250 -0
  13. holmes/core/models.py +157 -0
  14. holmes/core/openai_formatting.py +51 -0
  15. holmes/core/performance_timing.py +72 -0
  16. holmes/core/prompt.py +42 -0
  17. holmes/core/resource_instruction.py +17 -0
  18. holmes/core/runbooks.py +26 -0
  19. holmes/core/safeguards.py +120 -0
  20. holmes/core/supabase_dal.py +540 -0
  21. holmes/core/tool_calling_llm.py +798 -0
  22. holmes/core/tools.py +566 -0
  23. holmes/core/tools_utils/__init__.py +0 -0
  24. holmes/core/tools_utils/tool_executor.py +65 -0
  25. holmes/core/tools_utils/toolset_utils.py +52 -0
  26. holmes/core/toolset_manager.py +418 -0
  27. holmes/interactive.py +229 -0
  28. holmes/main.py +1041 -0
  29. holmes/plugins/__init__.py +0 -0
  30. holmes/plugins/destinations/__init__.py +6 -0
  31. holmes/plugins/destinations/slack/__init__.py +2 -0
  32. holmes/plugins/destinations/slack/plugin.py +163 -0
  33. holmes/plugins/interfaces.py +32 -0
  34. holmes/plugins/prompts/__init__.py +48 -0
  35. holmes/plugins/prompts/_current_date_time.jinja2 +1 -0
  36. holmes/plugins/prompts/_default_log_prompt.jinja2 +11 -0
  37. holmes/plugins/prompts/_fetch_logs.jinja2 +36 -0
  38. holmes/plugins/prompts/_general_instructions.jinja2 +86 -0
  39. holmes/plugins/prompts/_global_instructions.jinja2 +12 -0
  40. holmes/plugins/prompts/_runbook_instructions.jinja2 +13 -0
  41. holmes/plugins/prompts/_toolsets_instructions.jinja2 +56 -0
  42. holmes/plugins/prompts/generic_ask.jinja2 +36 -0
  43. holmes/plugins/prompts/generic_ask_conversation.jinja2 +32 -0
  44. holmes/plugins/prompts/generic_ask_for_issue_conversation.jinja2 +50 -0
  45. holmes/plugins/prompts/generic_investigation.jinja2 +42 -0
  46. holmes/plugins/prompts/generic_post_processing.jinja2 +13 -0
  47. holmes/plugins/prompts/generic_ticket.jinja2 +12 -0
  48. holmes/plugins/prompts/investigation_output_format.jinja2 +32 -0
  49. holmes/plugins/prompts/kubernetes_workload_ask.jinja2 +84 -0
  50. holmes/plugins/prompts/kubernetes_workload_chat.jinja2 +39 -0
  51. holmes/plugins/runbooks/README.md +22 -0
  52. holmes/plugins/runbooks/__init__.py +100 -0
  53. holmes/plugins/runbooks/catalog.json +14 -0
  54. holmes/plugins/runbooks/jira.yaml +12 -0
  55. holmes/plugins/runbooks/kube-prometheus-stack.yaml +10 -0
  56. holmes/plugins/runbooks/networking/dns_troubleshooting_instructions.md +66 -0
  57. holmes/plugins/runbooks/upgrade/upgrade_troubleshooting_instructions.md +44 -0
  58. holmes/plugins/sources/github/__init__.py +77 -0
  59. holmes/plugins/sources/jira/__init__.py +123 -0
  60. holmes/plugins/sources/opsgenie/__init__.py +93 -0
  61. holmes/plugins/sources/pagerduty/__init__.py +147 -0
  62. holmes/plugins/sources/prometheus/__init__.py +0 -0
  63. holmes/plugins/sources/prometheus/models.py +104 -0
  64. holmes/plugins/sources/prometheus/plugin.py +154 -0
  65. holmes/plugins/toolsets/__init__.py +171 -0
  66. holmes/plugins/toolsets/aks-node-health.yaml +65 -0
  67. holmes/plugins/toolsets/aks.yaml +86 -0
  68. holmes/plugins/toolsets/argocd.yaml +70 -0
  69. holmes/plugins/toolsets/atlas_mongodb/instructions.jinja2 +8 -0
  70. holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py +307 -0
  71. holmes/plugins/toolsets/aws.yaml +76 -0
  72. holmes/plugins/toolsets/azure_sql/__init__.py +0 -0
  73. holmes/plugins/toolsets/azure_sql/apis/alert_monitoring_api.py +600 -0
  74. holmes/plugins/toolsets/azure_sql/apis/azure_sql_api.py +309 -0
  75. holmes/plugins/toolsets/azure_sql/apis/connection_failure_api.py +445 -0
  76. holmes/plugins/toolsets/azure_sql/apis/connection_monitoring_api.py +251 -0
  77. holmes/plugins/toolsets/azure_sql/apis/storage_analysis_api.py +317 -0
  78. holmes/plugins/toolsets/azure_sql/azure_base_toolset.py +55 -0
  79. holmes/plugins/toolsets/azure_sql/azure_sql_instructions.jinja2 +137 -0
  80. holmes/plugins/toolsets/azure_sql/azure_sql_toolset.py +183 -0
  81. holmes/plugins/toolsets/azure_sql/install.md +66 -0
  82. holmes/plugins/toolsets/azure_sql/tools/__init__.py +1 -0
  83. holmes/plugins/toolsets/azure_sql/tools/analyze_connection_failures.py +324 -0
  84. holmes/plugins/toolsets/azure_sql/tools/analyze_database_connections.py +243 -0
  85. holmes/plugins/toolsets/azure_sql/tools/analyze_database_health_status.py +205 -0
  86. holmes/plugins/toolsets/azure_sql/tools/analyze_database_performance.py +249 -0
  87. holmes/plugins/toolsets/azure_sql/tools/analyze_database_storage.py +373 -0
  88. holmes/plugins/toolsets/azure_sql/tools/get_active_alerts.py +237 -0
  89. holmes/plugins/toolsets/azure_sql/tools/get_slow_queries.py +172 -0
  90. holmes/plugins/toolsets/azure_sql/tools/get_top_cpu_queries.py +170 -0
  91. holmes/plugins/toolsets/azure_sql/tools/get_top_data_io_queries.py +188 -0
  92. holmes/plugins/toolsets/azure_sql/tools/get_top_log_io_queries.py +180 -0
  93. holmes/plugins/toolsets/azure_sql/utils.py +83 -0
  94. holmes/plugins/toolsets/bash/__init__.py +0 -0
  95. holmes/plugins/toolsets/bash/bash_instructions.jinja2 +14 -0
  96. holmes/plugins/toolsets/bash/bash_toolset.py +208 -0
  97. holmes/plugins/toolsets/bash/common/bash.py +52 -0
  98. holmes/plugins/toolsets/bash/common/config.py +14 -0
  99. holmes/plugins/toolsets/bash/common/stringify.py +25 -0
  100. holmes/plugins/toolsets/bash/common/validators.py +24 -0
  101. holmes/plugins/toolsets/bash/grep/__init__.py +52 -0
  102. holmes/plugins/toolsets/bash/kubectl/__init__.py +100 -0
  103. holmes/plugins/toolsets/bash/kubectl/constants.py +96 -0
  104. holmes/plugins/toolsets/bash/kubectl/kubectl_describe.py +66 -0
  105. holmes/plugins/toolsets/bash/kubectl/kubectl_events.py +88 -0
  106. holmes/plugins/toolsets/bash/kubectl/kubectl_get.py +108 -0
  107. holmes/plugins/toolsets/bash/kubectl/kubectl_logs.py +20 -0
  108. holmes/plugins/toolsets/bash/kubectl/kubectl_run.py +46 -0
  109. holmes/plugins/toolsets/bash/kubectl/kubectl_top.py +81 -0
  110. holmes/plugins/toolsets/bash/parse_command.py +103 -0
  111. holmes/plugins/toolsets/confluence.yaml +19 -0
  112. holmes/plugins/toolsets/consts.py +5 -0
  113. holmes/plugins/toolsets/coralogix/api.py +158 -0
  114. holmes/plugins/toolsets/coralogix/toolset_coralogix_logs.py +103 -0
  115. holmes/plugins/toolsets/coralogix/utils.py +181 -0
  116. holmes/plugins/toolsets/datadog.py +153 -0
  117. holmes/plugins/toolsets/docker.yaml +46 -0
  118. holmes/plugins/toolsets/git.py +756 -0
  119. holmes/plugins/toolsets/grafana/__init__.py +0 -0
  120. holmes/plugins/toolsets/grafana/base_grafana_toolset.py +54 -0
  121. holmes/plugins/toolsets/grafana/common.py +68 -0
  122. holmes/plugins/toolsets/grafana/grafana_api.py +31 -0
  123. holmes/plugins/toolsets/grafana/loki_api.py +89 -0
  124. holmes/plugins/toolsets/grafana/tempo_api.py +124 -0
  125. holmes/plugins/toolsets/grafana/toolset_grafana.py +102 -0
  126. holmes/plugins/toolsets/grafana/toolset_grafana_loki.py +102 -0
  127. holmes/plugins/toolsets/grafana/toolset_grafana_tempo.jinja2 +10 -0
  128. holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py +299 -0
  129. holmes/plugins/toolsets/grafana/trace_parser.py +195 -0
  130. holmes/plugins/toolsets/helm.yaml +42 -0
  131. holmes/plugins/toolsets/internet/internet.py +275 -0
  132. holmes/plugins/toolsets/internet/notion.py +137 -0
  133. holmes/plugins/toolsets/kafka.py +638 -0
  134. holmes/plugins/toolsets/kubernetes.yaml +255 -0
  135. holmes/plugins/toolsets/kubernetes_logs.py +426 -0
  136. holmes/plugins/toolsets/kubernetes_logs.yaml +42 -0
  137. holmes/plugins/toolsets/logging_utils/__init__.py +0 -0
  138. holmes/plugins/toolsets/logging_utils/logging_api.py +217 -0
  139. holmes/plugins/toolsets/logging_utils/types.py +0 -0
  140. holmes/plugins/toolsets/mcp/toolset_mcp.py +135 -0
  141. holmes/plugins/toolsets/newrelic.py +222 -0
  142. holmes/plugins/toolsets/opensearch/__init__.py +0 -0
  143. holmes/plugins/toolsets/opensearch/opensearch.py +245 -0
  144. holmes/plugins/toolsets/opensearch/opensearch_logs.py +151 -0
  145. holmes/plugins/toolsets/opensearch/opensearch_traces.py +211 -0
  146. holmes/plugins/toolsets/opensearch/opensearch_traces_instructions.jinja2 +12 -0
  147. holmes/plugins/toolsets/opensearch/opensearch_utils.py +166 -0
  148. holmes/plugins/toolsets/prometheus/prometheus.py +818 -0
  149. holmes/plugins/toolsets/prometheus/prometheus_instructions.jinja2 +38 -0
  150. holmes/plugins/toolsets/rabbitmq/api.py +398 -0
  151. holmes/plugins/toolsets/rabbitmq/rabbitmq_instructions.jinja2 +37 -0
  152. holmes/plugins/toolsets/rabbitmq/toolset_rabbitmq.py +222 -0
  153. holmes/plugins/toolsets/robusta/__init__.py +0 -0
  154. holmes/plugins/toolsets/robusta/robusta.py +235 -0
  155. holmes/plugins/toolsets/robusta/robusta_instructions.jinja2 +24 -0
  156. holmes/plugins/toolsets/runbook/__init__.py +0 -0
  157. holmes/plugins/toolsets/runbook/runbook_fetcher.py +78 -0
  158. holmes/plugins/toolsets/service_discovery.py +92 -0
  159. holmes/plugins/toolsets/servicenow/install.md +37 -0
  160. holmes/plugins/toolsets/servicenow/instructions.jinja2 +3 -0
  161. holmes/plugins/toolsets/servicenow/servicenow.py +198 -0
  162. holmes/plugins/toolsets/slab.yaml +20 -0
  163. holmes/plugins/toolsets/utils.py +137 -0
  164. holmes/plugins/utils.py +14 -0
  165. holmes/utils/__init__.py +0 -0
  166. holmes/utils/cache.py +84 -0
  167. holmes/utils/cert_utils.py +40 -0
  168. holmes/utils/default_toolset_installation_guide.jinja2 +44 -0
  169. holmes/utils/definitions.py +13 -0
  170. holmes/utils/env.py +53 -0
  171. holmes/utils/file_utils.py +56 -0
  172. holmes/utils/global_instructions.py +20 -0
  173. holmes/utils/holmes_status.py +22 -0
  174. holmes/utils/holmes_sync_toolsets.py +80 -0
  175. holmes/utils/markdown_utils.py +55 -0
  176. holmes/utils/pydantic_utils.py +54 -0
  177. holmes/utils/robusta.py +10 -0
  178. holmes/utils/tags.py +97 -0
  179. holmesgpt-0.11.5.dist-info/LICENSE.txt +21 -0
  180. holmesgpt-0.11.5.dist-info/METADATA +400 -0
  181. holmesgpt-0.11.5.dist-info/RECORD +183 -0
  182. holmesgpt-0.11.5.dist-info/WHEEL +4 -0
  183. holmesgpt-0.11.5.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,275 @@
1
+ import re
2
+ import os
3
+ import logging
4
+ from typing import Any, Optional, Tuple, Dict, List
5
+
6
+ from requests import RequestException, Timeout # type: ignore
7
+ from holmes.core.tools import (
8
+ Tool,
9
+ ToolParameter,
10
+ Toolset,
11
+ ToolsetTag,
12
+ CallablePrerequisite,
13
+ )
14
+ from markdownify import markdownify
15
+ from bs4 import BeautifulSoup
16
+
17
+ import requests # type: ignore
18
+ from holmes.core.tools import StructuredToolResult, ToolResultStatus
19
+
20
+
21
+ # TODO: change and make it holmes
22
+ INTERNET_TOOLSET_USER_AGENT = os.environ.get(
23
+ "INTERNET_TOOLSET_USER_AGENT",
24
+ "Mozilla/5.0 (X11; Linux x86_64; rv:128.0; holmesgpt;) Gecko/20100101 Firefox/128.0",
25
+ )
26
+ INTERNET_TOOLSET_TIMEOUT_SECONDS = int(
27
+ os.environ.get("INTERNET_TOOLSET_TIMEOUT_SECONDS", "60")
28
+ )
29
+
30
+ SELECTORS_TO_REMOVE = [
31
+ "script",
32
+ "style",
33
+ "link",
34
+ "noscript",
35
+ "header",
36
+ "footer",
37
+ "nav",
38
+ "iframe",
39
+ "svg",
40
+ "img",
41
+ "button",
42
+ "menu",
43
+ "sidebar",
44
+ "aside",
45
+ ".header",
46
+ ".footer",
47
+ ".navigation",
48
+ ".nav",
49
+ ".menu",
50
+ ".sidebar",
51
+ ".ad",
52
+ ".advertisement",
53
+ ".social",
54
+ ".popup",
55
+ ".modal",
56
+ ".banner",
57
+ ".cookie-notice",
58
+ ".social-share",
59
+ ".related-articles",
60
+ ".recommended",
61
+ "#header",
62
+ "#footer",
63
+ "#navigation",
64
+ "#nav",
65
+ "#menu",
66
+ "#sidebar",
67
+ "#ad",
68
+ "#advertisement",
69
+ "#social",
70
+ "#popup",
71
+ "#modal",
72
+ "#banner",
73
+ "#cookie-notice",
74
+ "#social-share",
75
+ "#related-articles",
76
+ "#recommended",
77
+ ]
78
+
79
+
80
+ def scrape(url: str, headers: Dict[str, str]) -> Tuple[Optional[str], Optional[str]]:
81
+ response = None
82
+ content = None
83
+ mime_type = None
84
+ if not headers:
85
+ headers = {}
86
+ headers["User-Agent"] = INTERNET_TOOLSET_USER_AGENT
87
+ try:
88
+ response = requests.get(
89
+ url,
90
+ headers=headers,
91
+ timeout=INTERNET_TOOLSET_TIMEOUT_SECONDS,
92
+ )
93
+ response.raise_for_status()
94
+ except Timeout:
95
+ error_message = f"Failed to load {url}. Timeout after {INTERNET_TOOLSET_TIMEOUT_SECONDS} seconds"
96
+ logging.error(
97
+ error_message,
98
+ exc_info=True,
99
+ )
100
+ return error_message, None
101
+ except RequestException as e:
102
+ error_message = f"Failed to load {url}: {str(e)}"
103
+ logging.warning(error_message, exc_info=True)
104
+ return error_message, None
105
+
106
+ if response:
107
+ content = response.text
108
+ try:
109
+ content_type = response.headers["content-type"]
110
+ if content_type:
111
+ mime_type = content_type.split(";")[0]
112
+ except Exception:
113
+ logging.info(
114
+ f"Failed to parse content type from headers {response.headers}"
115
+ )
116
+
117
+ return (content, mime_type)
118
+
119
+
120
+ def cleanup(soup: BeautifulSoup):
121
+ """Remove all elements that are irrelevant to the textual representation of a web page.
122
+ This includes images, extra data, even links as there is no intention to navigate from that page.
123
+ """
124
+
125
+ for selector in SELECTORS_TO_REMOVE:
126
+ for element in soup.select(selector):
127
+ element.decompose()
128
+
129
+ for tag in soup.find_all(True):
130
+ for attr in list(tag.attrs): # type: ignore
131
+ if attr != "href":
132
+ tag.attrs.pop(attr, None) # type: ignore
133
+
134
+ return soup
135
+
136
+
137
+ def html_to_markdown(page_source: str):
138
+ soup = BeautifulSoup(page_source, "html.parser")
139
+ soup = cleanup(soup)
140
+ page_source = str(soup)
141
+
142
+ try:
143
+ md = markdownify(page_source)
144
+ except OSError as e:
145
+ logging.error(
146
+ f"There was an error in converting the HTML to markdown. Falling back to returning the raw HTML. Error: {str(e)}"
147
+ )
148
+ return page_source
149
+
150
+ md = re.sub(r"</div>", " ", md)
151
+ md = re.sub(r"<div>", " ", md)
152
+
153
+ md = re.sub(r"\n\s*\n", "\n\n", md)
154
+
155
+ return md
156
+
157
+
158
+ def looks_like_html(content):
159
+ """
160
+ Check if the content looks like HTML.
161
+ """
162
+ if isinstance(content, str):
163
+ # Check for common HTML tags
164
+ html_patterns = [r"<!DOCTYPE\s+html", r"<html", r"<head", r"<body"]
165
+ return any(
166
+ re.search(pattern, content, re.IGNORECASE) for pattern in html_patterns
167
+ )
168
+ return False
169
+
170
+
171
+ class FetchWebpage(Tool):
172
+ toolset: "InternetToolset"
173
+
174
+ def __init__(self, toolset: "InternetToolset"):
175
+ super().__init__(
176
+ name="fetch_webpage",
177
+ description="Fetch a webpage. Use this to fetch runbooks if they are present before starting your investigation (if no other tool like confluence is more appropriate)",
178
+ parameters={
179
+ "url": ToolParameter(
180
+ description="The URL to fetch",
181
+ type="string",
182
+ required=True,
183
+ ),
184
+ },
185
+ toolset=toolset, # type: ignore
186
+ )
187
+
188
+ def _invoke(self, params: Any) -> StructuredToolResult:
189
+ url: str = params["url"]
190
+
191
+ additional_headers = (
192
+ self.toolset.additional_headers if self.toolset.additional_headers else {}
193
+ )
194
+ content, mime_type = scrape(url, additional_headers)
195
+
196
+ if not content:
197
+ logging.error(f"Failed to retrieve content from {url}")
198
+ return StructuredToolResult(
199
+ status=ToolResultStatus.ERROR,
200
+ error=f"Failed to retrieve content from {url}",
201
+ params=params,
202
+ )
203
+
204
+ # Check if the content is HTML based on MIME type or content
205
+ if (mime_type and mime_type.startswith("text/html")) or (
206
+ mime_type is None and looks_like_html(content)
207
+ ):
208
+ content = html_to_markdown(content)
209
+
210
+ return StructuredToolResult(
211
+ status=ToolResultStatus.SUCCESS,
212
+ data=content,
213
+ params=params,
214
+ )
215
+
216
+ def get_parameterized_one_liner(self, params) -> str:
217
+ url: str = params["url"]
218
+ return f"fetched webpage {url}"
219
+
220
+
221
+ class InternetBaseToolset(Toolset):
222
+ additional_headers: Dict[str, str] = {}
223
+
224
+ def __init__(
225
+ self,
226
+ name: str,
227
+ description: str,
228
+ icon_url: str,
229
+ tools: list[Tool],
230
+ is_default: bool,
231
+ tags: List[ToolsetTag],
232
+ docs_url: Optional[str] = None,
233
+ ):
234
+ super().__init__(
235
+ name=name,
236
+ description=description,
237
+ icon_url=icon_url,
238
+ prerequisites=[
239
+ CallablePrerequisite(callable=self.prerequisites_callable),
240
+ ],
241
+ tools=tools,
242
+ tags=tags,
243
+ is_default=is_default,
244
+ docs_url=docs_url,
245
+ )
246
+
247
+ def prerequisites_callable(self, config: Dict[str, Any]) -> Tuple[bool, str]:
248
+ if not config:
249
+ return True, ""
250
+ self.additional_headers = config.get("additional_headers", {})
251
+ return True, ""
252
+
253
+ def get_example_config(self) -> Dict[str, Any]:
254
+ return {
255
+ "additional_headers": {"Authorization": "Basic <base_64_encoded_string>"}
256
+ }
257
+
258
+
259
+ class InternetToolset(InternetBaseToolset):
260
+ additional_headers: Dict[str, str] = {}
261
+
262
+ def __init__(self):
263
+ super().__init__(
264
+ name="internet",
265
+ description="Fetch webpages",
266
+ icon_url="https://platform.robusta.dev/demos/internet-access.svg",
267
+ tools=[
268
+ FetchWebpage(self),
269
+ ],
270
+ docs_url="https://docs.robusta.dev/master/configuration/holmesgpt/toolsets/internet.html",
271
+ tags=[
272
+ ToolsetTag.CORE,
273
+ ],
274
+ is_default=True,
275
+ )
@@ -0,0 +1,137 @@
1
+ import re
2
+ import logging
3
+ import json
4
+ from typing import Any, Dict, Tuple
5
+ from holmes.core.tools import (
6
+ Tool,
7
+ ToolParameter,
8
+ ToolsetTag,
9
+ )
10
+ from holmes.plugins.toolsets.internet.internet import (
11
+ InternetBaseToolset,
12
+ scrape,
13
+ )
14
+ from holmes.core.tools import (
15
+ StructuredToolResult,
16
+ ToolResultStatus,
17
+ )
18
+
19
+
20
+ class FetchNotion(Tool):
21
+ toolset: "InternetBaseToolset"
22
+
23
+ def __init__(self, toolset: "InternetBaseToolset"):
24
+ super().__init__(
25
+ name="fetch_notion_webpage",
26
+ description="Fetch a Notion webpage with HTTP requests and authentication.",
27
+ parameters={
28
+ "url": ToolParameter(
29
+ description="The URL to fetch",
30
+ type="string",
31
+ required=True,
32
+ ),
33
+ },
34
+ toolset=toolset, # type: ignore
35
+ )
36
+
37
+ def convert_notion_url(self, url):
38
+ if "api.notion.com" in url:
39
+ return url
40
+ match = re.search(r"-(\w{32})$", url)
41
+ if match:
42
+ notion_id = match.group(1)
43
+ return f"https://api.notion.com/v1/blocks/{notion_id}/children"
44
+ return url # Return original URL if no match is found
45
+
46
+ def _invoke(self, params: Any) -> StructuredToolResult:
47
+ url: str = params["url"]
48
+
49
+ # Get headers from the toolset configuration
50
+ additional_headers = (
51
+ self.toolset.additional_headers if self.toolset.additional_headers else {}
52
+ )
53
+ url = self.convert_notion_url(url)
54
+ content, _ = scrape(url, additional_headers)
55
+
56
+ if not content:
57
+ logging.error(f"Failed to retrieve content from {url}")
58
+ return StructuredToolResult(
59
+ status=ToolResultStatus.ERROR,
60
+ error=f"Failed to retrieve content from {url}",
61
+ params=params,
62
+ )
63
+
64
+ return StructuredToolResult(
65
+ status=ToolResultStatus.SUCCESS,
66
+ data=self.parse_notion_content(content),
67
+ params=params,
68
+ )
69
+
70
+ def parse_notion_content(self, content: Any) -> str:
71
+ data = json.loads(content)
72
+ texts = []
73
+
74
+ for result in data.get("results", []):
75
+ # Handle paragraph blocks
76
+ if result.get("type") == "paragraph":
77
+ rich_texts = result["paragraph"].get("rich_text", [])
78
+ formatted_text = self.format_rich_text(rich_texts)
79
+ if formatted_text:
80
+ texts.append(formatted_text)
81
+
82
+ # Handle bulleted list items
83
+ elif result.get("type") == "bulleted_list_item":
84
+ rich_texts = result["bulleted_list_item"].get("rich_text", [])
85
+ formatted_text = self.format_rich_text(rich_texts)
86
+ if formatted_text:
87
+ texts.append(f"- {formatted_text}")
88
+
89
+ # Join and return the formatted text
90
+ return "\n\n".join(texts)
91
+
92
+ def format_rich_text(self, rich_texts: list) -> str:
93
+ """Helper function to apply formatting (bold, code, etc.)"""
94
+ formatted_text = []
95
+ for text in rich_texts:
96
+ plain_text = text["text"]["content"]
97
+ annotations = text.get("annotations", {})
98
+
99
+ # Apply formatting
100
+ if annotations.get("bold"):
101
+ plain_text = f"**{plain_text}**"
102
+ if annotations.get("code"):
103
+ plain_text = f"`{plain_text}`"
104
+
105
+ formatted_text.append(plain_text)
106
+
107
+ return "".join(formatted_text)
108
+
109
+ def get_parameterized_one_liner(self, params) -> str:
110
+ url: str = params["url"]
111
+ return f"fetched notion webpage {url}"
112
+
113
+
114
+ class NotionToolset(InternetBaseToolset):
115
+ def __init__(self):
116
+ super().__init__(
117
+ name="notion",
118
+ description="Fetch notion webpages",
119
+ icon_url="https://upload.wikimedia.org/wikipedia/commons/thumb/e/e9/Notion-logo.svg/2048px-Notion-logo.svg.png",
120
+ docs_url="https://docs.robusta.dev/master/configuration/holmesgpt/toolsets/notion.html",
121
+ tools=[
122
+ FetchNotion(self),
123
+ ],
124
+ tags=[
125
+ ToolsetTag.CORE,
126
+ ],
127
+ is_default=False,
128
+ )
129
+
130
+ def prerequisites_callable(self, config: Dict[str, Any]) -> Tuple[bool, str]:
131
+ if not config or not config.get("additional_headers", {}):
132
+ return (
133
+ False,
134
+ "Notion toolset is misconfigured. Authorization header is required.",
135
+ )
136
+ self.additional_headers = config["additional_headers"]
137
+ return True, ""