holmesgpt 0.13.2__py3-none-any.whl → 0.16.2a0__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.
Files changed (134) hide show
  1. holmes/__init__.py +1 -1
  2. holmes/clients/robusta_client.py +17 -4
  3. holmes/common/env_vars.py +40 -1
  4. holmes/config.py +114 -144
  5. holmes/core/conversations.py +53 -14
  6. holmes/core/feedback.py +191 -0
  7. holmes/core/investigation.py +18 -22
  8. holmes/core/llm.py +489 -88
  9. holmes/core/models.py +103 -1
  10. holmes/core/openai_formatting.py +13 -0
  11. holmes/core/prompt.py +1 -1
  12. holmes/core/safeguards.py +4 -4
  13. holmes/core/supabase_dal.py +293 -100
  14. holmes/core/tool_calling_llm.py +423 -323
  15. holmes/core/tools.py +311 -33
  16. holmes/core/tools_utils/token_counting.py +14 -0
  17. holmes/core/tools_utils/tool_context_window_limiter.py +57 -0
  18. holmes/core/tools_utils/tool_executor.py +13 -8
  19. holmes/core/toolset_manager.py +155 -4
  20. holmes/core/tracing.py +6 -1
  21. holmes/core/transformers/__init__.py +23 -0
  22. holmes/core/transformers/base.py +62 -0
  23. holmes/core/transformers/llm_summarize.py +174 -0
  24. holmes/core/transformers/registry.py +122 -0
  25. holmes/core/transformers/transformer.py +31 -0
  26. holmes/core/truncation/compaction.py +59 -0
  27. holmes/core/truncation/dal_truncation_utils.py +23 -0
  28. holmes/core/truncation/input_context_window_limiter.py +218 -0
  29. holmes/interactive.py +177 -24
  30. holmes/main.py +7 -4
  31. holmes/plugins/prompts/_fetch_logs.jinja2 +26 -1
  32. holmes/plugins/prompts/_general_instructions.jinja2 +1 -2
  33. holmes/plugins/prompts/_runbook_instructions.jinja2 +23 -12
  34. holmes/plugins/prompts/conversation_history_compaction.jinja2 +88 -0
  35. holmes/plugins/prompts/generic_ask.jinja2 +2 -4
  36. holmes/plugins/prompts/generic_ask_conversation.jinja2 +2 -1
  37. holmes/plugins/prompts/generic_ask_for_issue_conversation.jinja2 +2 -1
  38. holmes/plugins/prompts/generic_investigation.jinja2 +2 -1
  39. holmes/plugins/prompts/investigation_procedure.jinja2 +48 -0
  40. holmes/plugins/prompts/kubernetes_workload_ask.jinja2 +2 -1
  41. holmes/plugins/prompts/kubernetes_workload_chat.jinja2 +2 -1
  42. holmes/plugins/runbooks/__init__.py +117 -18
  43. holmes/plugins/runbooks/catalog.json +2 -0
  44. holmes/plugins/toolsets/__init__.py +21 -8
  45. holmes/plugins/toolsets/aks-node-health.yaml +46 -0
  46. holmes/plugins/toolsets/aks.yaml +64 -0
  47. holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py +26 -36
  48. holmes/plugins/toolsets/azure_sql/azure_sql_toolset.py +0 -1
  49. holmes/plugins/toolsets/azure_sql/tools/analyze_connection_failures.py +10 -7
  50. holmes/plugins/toolsets/azure_sql/tools/analyze_database_connections.py +9 -6
  51. holmes/plugins/toolsets/azure_sql/tools/analyze_database_health_status.py +8 -6
  52. holmes/plugins/toolsets/azure_sql/tools/analyze_database_performance.py +8 -6
  53. holmes/plugins/toolsets/azure_sql/tools/analyze_database_storage.py +9 -6
  54. holmes/plugins/toolsets/azure_sql/tools/get_active_alerts.py +9 -7
  55. holmes/plugins/toolsets/azure_sql/tools/get_slow_queries.py +9 -6
  56. holmes/plugins/toolsets/azure_sql/tools/get_top_cpu_queries.py +9 -6
  57. holmes/plugins/toolsets/azure_sql/tools/get_top_data_io_queries.py +9 -6
  58. holmes/plugins/toolsets/azure_sql/tools/get_top_log_io_queries.py +9 -6
  59. holmes/plugins/toolsets/bash/bash_toolset.py +10 -13
  60. holmes/plugins/toolsets/bash/common/bash.py +7 -7
  61. holmes/plugins/toolsets/cilium.yaml +284 -0
  62. holmes/plugins/toolsets/coralogix/toolset_coralogix_logs.py +5 -3
  63. holmes/plugins/toolsets/datadog/datadog_api.py +490 -24
  64. holmes/plugins/toolsets/datadog/datadog_logs_instructions.jinja2 +21 -10
  65. holmes/plugins/toolsets/datadog/toolset_datadog_general.py +349 -216
  66. holmes/plugins/toolsets/datadog/toolset_datadog_logs.py +190 -19
  67. holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py +101 -44
  68. holmes/plugins/toolsets/datadog/toolset_datadog_rds.py +13 -16
  69. holmes/plugins/toolsets/datadog/toolset_datadog_traces.py +25 -31
  70. holmes/plugins/toolsets/git.py +51 -46
  71. holmes/plugins/toolsets/grafana/common.py +15 -3
  72. holmes/plugins/toolsets/grafana/grafana_api.py +46 -24
  73. holmes/plugins/toolsets/grafana/grafana_tempo_api.py +454 -0
  74. holmes/plugins/toolsets/grafana/loki/instructions.jinja2 +9 -0
  75. holmes/plugins/toolsets/grafana/loki/toolset_grafana_loki.py +117 -0
  76. holmes/plugins/toolsets/grafana/toolset_grafana.py +211 -91
  77. holmes/plugins/toolsets/grafana/toolset_grafana_dashboard.jinja2 +27 -0
  78. holmes/plugins/toolsets/grafana/toolset_grafana_tempo.jinja2 +246 -11
  79. holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py +653 -293
  80. holmes/plugins/toolsets/grafana/trace_parser.py +1 -1
  81. holmes/plugins/toolsets/internet/internet.py +6 -7
  82. holmes/plugins/toolsets/internet/notion.py +5 -6
  83. holmes/plugins/toolsets/investigator/core_investigation.py +42 -34
  84. holmes/plugins/toolsets/kafka.py +25 -36
  85. holmes/plugins/toolsets/kubernetes.yaml +58 -84
  86. holmes/plugins/toolsets/kubernetes_logs.py +6 -6
  87. holmes/plugins/toolsets/kubernetes_logs.yaml +32 -0
  88. holmes/plugins/toolsets/logging_utils/logging_api.py +80 -4
  89. holmes/plugins/toolsets/mcp/toolset_mcp.py +181 -55
  90. holmes/plugins/toolsets/newrelic/__init__.py +0 -0
  91. holmes/plugins/toolsets/newrelic/new_relic_api.py +125 -0
  92. holmes/plugins/toolsets/newrelic/newrelic.jinja2 +41 -0
  93. holmes/plugins/toolsets/newrelic/newrelic.py +163 -0
  94. holmes/plugins/toolsets/opensearch/opensearch.py +10 -17
  95. holmes/plugins/toolsets/opensearch/opensearch_logs.py +7 -7
  96. holmes/plugins/toolsets/opensearch/opensearch_ppl_query_docs.jinja2 +1616 -0
  97. holmes/plugins/toolsets/opensearch/opensearch_query_assist.py +78 -0
  98. holmes/plugins/toolsets/opensearch/opensearch_query_assist_instructions.jinja2 +223 -0
  99. holmes/plugins/toolsets/opensearch/opensearch_traces.py +13 -16
  100. holmes/plugins/toolsets/openshift.yaml +283 -0
  101. holmes/plugins/toolsets/prometheus/prometheus.py +915 -390
  102. holmes/plugins/toolsets/prometheus/prometheus_instructions.jinja2 +43 -2
  103. holmes/plugins/toolsets/prometheus/utils.py +28 -0
  104. holmes/plugins/toolsets/rabbitmq/toolset_rabbitmq.py +9 -10
  105. holmes/plugins/toolsets/robusta/robusta.py +236 -65
  106. holmes/plugins/toolsets/robusta/robusta_instructions.jinja2 +26 -9
  107. holmes/plugins/toolsets/runbook/runbook_fetcher.py +137 -26
  108. holmes/plugins/toolsets/service_discovery.py +1 -1
  109. holmes/plugins/toolsets/servicenow_tables/instructions.jinja2 +83 -0
  110. holmes/plugins/toolsets/servicenow_tables/servicenow_tables.py +426 -0
  111. holmes/plugins/toolsets/utils.py +88 -0
  112. holmes/utils/config_utils.py +91 -0
  113. holmes/utils/default_toolset_installation_guide.jinja2 +1 -22
  114. holmes/utils/env.py +7 -0
  115. holmes/utils/global_instructions.py +75 -10
  116. holmes/utils/holmes_status.py +2 -1
  117. holmes/utils/holmes_sync_toolsets.py +0 -2
  118. holmes/utils/krr_utils.py +188 -0
  119. holmes/utils/sentry_helper.py +41 -0
  120. holmes/utils/stream.py +61 -7
  121. holmes/version.py +34 -14
  122. holmesgpt-0.16.2a0.dist-info/LICENSE +178 -0
  123. {holmesgpt-0.13.2.dist-info → holmesgpt-0.16.2a0.dist-info}/METADATA +29 -27
  124. {holmesgpt-0.13.2.dist-info → holmesgpt-0.16.2a0.dist-info}/RECORD +126 -102
  125. holmes/core/performance_timing.py +0 -72
  126. holmes/plugins/toolsets/grafana/tempo_api.py +0 -124
  127. holmes/plugins/toolsets/grafana/toolset_grafana_loki.py +0 -110
  128. holmes/plugins/toolsets/newrelic.py +0 -231
  129. holmes/plugins/toolsets/servicenow/install.md +0 -37
  130. holmes/plugins/toolsets/servicenow/instructions.jinja2 +0 -3
  131. holmes/plugins/toolsets/servicenow/servicenow.py +0 -219
  132. holmesgpt-0.13.2.dist-info/LICENSE.txt +0 -21
  133. {holmesgpt-0.13.2.dist-info → holmesgpt-0.16.2a0.dist-info}/WHEEL +0 -0
  134. {holmesgpt-0.13.2.dist-info → holmesgpt-0.16.2a0.dist-info}/entry_points.txt +0 -0
@@ -9,8 +9,9 @@ from holmes.core.tools import (
9
9
  CallablePrerequisite,
10
10
  StructuredToolResult,
11
11
  Tool,
12
+ ToolInvokeContext,
12
13
  ToolParameter,
13
- ToolResultStatus,
14
+ StructuredToolResultStatus,
14
15
  Toolset,
15
16
  ToolsetTag,
16
17
  )
@@ -69,7 +70,7 @@ class GenerateRDSPerformanceReport(BaseDatadogRDSTool):
69
70
  def __init__(self, toolset: "DatadogRDSToolset"):
70
71
  super().__init__(
71
72
  name="datadog_rds_performance_report",
72
- description="Generate a comprehensive performance report for a specific RDS instance including latency, resource utilization, and storage metrics with analysis",
73
+ description="[datadog/rds toolset] Generate a comprehensive performance report for a specific RDS instance including latency, resource utilization, and storage metrics with analysis",
73
74
  parameters={
74
75
  "db_instance_identifier": ToolParameter(
75
76
  description="The RDS database instance identifier",
@@ -92,12 +93,10 @@ class GenerateRDSPerformanceReport(BaseDatadogRDSTool):
92
93
  toolset=toolset,
93
94
  )
94
95
 
95
- def _invoke(
96
- self, params: dict, user_approved: bool = False
97
- ) -> StructuredToolResult:
96
+ def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
98
97
  if not self.toolset.dd_config:
99
98
  return StructuredToolResult(
100
- status=ToolResultStatus.ERROR,
99
+ status=StructuredToolResultStatus.ERROR,
101
100
  error=TOOLSET_CONFIG_MISSING_ERROR,
102
101
  params=params,
103
102
  )
@@ -150,7 +149,7 @@ class GenerateRDSPerformanceReport(BaseDatadogRDSTool):
150
149
  formatted_report = self._format_report(report)
151
150
 
152
151
  return StructuredToolResult(
153
- status=ToolResultStatus.SUCCESS,
152
+ status=StructuredToolResultStatus.SUCCESS,
154
153
  data=formatted_report,
155
154
  params=params,
156
155
  )
@@ -158,7 +157,7 @@ class GenerateRDSPerformanceReport(BaseDatadogRDSTool):
158
157
  except Exception as e:
159
158
  logging.error(f"Error generating RDS performance report: {str(e)}")
160
159
  return StructuredToolResult(
161
- status=ToolResultStatus.ERROR,
160
+ status=StructuredToolResultStatus.ERROR,
162
161
  error=f"Failed to generate RDS performance report: {str(e)}",
163
162
  params=params,
164
163
  )
@@ -364,7 +363,7 @@ class GetTopWorstPerformingRDSInstances(BaseDatadogRDSTool):
364
363
  def __init__(self, toolset: "DatadogRDSToolset"):
365
364
  super().__init__(
366
365
  name="datadog_rds_top_worst_performing",
367
- description="Get a summarized report of the top worst performing RDS instances based on latency, CPU utilization, and error rates",
366
+ description="[datadog/rds toolset] Get a summarized report of the top worst performing RDS instances based on latency, CPU utilization, and error rates",
368
367
  parameters={
369
368
  "top_n": ToolParameter(
370
369
  description=f"Number of worst performing instances to return (default: {DEFAULT_TOP_INSTANCES})",
@@ -392,12 +391,10 @@ class GetTopWorstPerformingRDSInstances(BaseDatadogRDSTool):
392
391
  toolset=toolset,
393
392
  )
394
393
 
395
- def _invoke(
396
- self, params: dict, user_approved: bool = False
397
- ) -> StructuredToolResult:
394
+ def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
398
395
  if not self.toolset.dd_config:
399
396
  return StructuredToolResult(
400
- status=ToolResultStatus.ERROR,
397
+ status=StructuredToolResultStatus.ERROR,
401
398
  error=TOOLSET_CONFIG_MISSING_ERROR,
402
399
  params=params,
403
400
  )
@@ -416,7 +413,7 @@ class GetTopWorstPerformingRDSInstances(BaseDatadogRDSTool):
416
413
 
417
414
  if not instances:
418
415
  return StructuredToolResult(
419
- status=ToolResultStatus.NO_DATA,
416
+ status=StructuredToolResultStatus.NO_DATA,
420
417
  data="No RDS instances found with metrics in the specified time range",
421
418
  params=params,
422
419
  )
@@ -443,7 +440,7 @@ class GetTopWorstPerformingRDSInstances(BaseDatadogRDSTool):
443
440
  report += f"\n\nInstances:\n{json.dumps(worst_performers, indent=2)}"
444
441
 
445
442
  return StructuredToolResult(
446
- status=ToolResultStatus.SUCCESS,
443
+ status=StructuredToolResultStatus.SUCCESS,
447
444
  data=report,
448
445
  params=params,
449
446
  )
@@ -451,7 +448,7 @@ class GetTopWorstPerformingRDSInstances(BaseDatadogRDSTool):
451
448
  except Exception as e:
452
449
  logging.error(f"Error getting top worst performing RDS instances: {str(e)}")
453
450
  return StructuredToolResult(
454
- status=ToolResultStatus.ERROR,
451
+ status=StructuredToolResultStatus.ERROR,
455
452
  error=f"Failed to get top worst performing RDS instances: {str(e)}",
456
453
  params=params,
457
454
  )
@@ -9,10 +9,11 @@ from typing import Any, Dict, Optional, Tuple
9
9
  from holmes.core.tools import (
10
10
  CallablePrerequisite,
11
11
  Tool,
12
+ ToolInvokeContext,
12
13
  ToolParameter,
13
14
  Toolset,
14
15
  StructuredToolResult,
15
- ToolResultStatus,
16
+ StructuredToolResultStatus,
16
17
  ToolsetTag,
17
18
  )
18
19
  from holmes.plugins.toolsets.datadog.datadog_api import (
@@ -49,7 +50,7 @@ class DatadogTracesToolset(Toolset):
49
50
  super().__init__(
50
51
  name="datadog/traces",
51
52
  description="Toolset for interacting with Datadog APM to fetch and analyze traces",
52
- docs_url="https://docs.datadoghq.com/api/latest/spans/",
53
+ docs_url="https://holmesgpt.dev/data-sources/builtin-toolsets/datadog/",
53
54
  icon_url="https://imgix.datadoghq.com//img/about/presskit/DDlogo.jpg",
54
55
  prerequisites=[CallablePrerequisite(callable=self.prerequisites_callable)],
55
56
  tools=[
@@ -57,7 +58,6 @@ class DatadogTracesToolset(Toolset):
57
58
  FetchDatadogTraceById(toolset=self),
58
59
  FetchDatadogSpansByFilter(toolset=self),
59
60
  ],
60
- experimental=True,
61
61
  tags=[ToolsetTag.CORE],
62
62
  )
63
63
  self._reload_instructions()
@@ -157,7 +157,7 @@ class FetchDatadogTracesList(BaseDatadogTracesTool):
157
157
  def __init__(self, toolset: "DatadogTracesToolset"):
158
158
  super().__init__(
159
159
  name="fetch_datadog_traces",
160
- description="Fetch a list of traces from Datadog with optional filters",
160
+ description="[datadog/traces toolset] Fetch a list of traces from Datadog with optional filters",
161
161
  parameters={
162
162
  "service": ToolParameter(
163
163
  description="Filter by service name",
@@ -211,13 +211,11 @@ class FetchDatadogTracesList(BaseDatadogTracesTool):
211
211
  filter_str = ", ".join(filters) if filters else "all"
212
212
  return f"{toolset_name_for_one_liner(self.toolset.name)}: Fetch Traces ({filter_str})"
213
213
 
214
- def _invoke(
215
- self, params: dict, user_approved: bool = False
216
- ) -> StructuredToolResult:
214
+ def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
217
215
  """Execute the tool to fetch traces."""
218
216
  if not self.toolset.dd_config:
219
217
  return StructuredToolResult(
220
- status=ToolResultStatus.ERROR,
218
+ status=StructuredToolResultStatus.ERROR,
221
219
  error="Datadog configuration not initialized",
222
220
  params=params,
223
221
  )
@@ -306,13 +304,13 @@ class FetchDatadogTracesList(BaseDatadogTracesTool):
306
304
  formatted_output = format_traces_list(spans, limit=params.get("limit", 50))
307
305
  if not formatted_output:
308
306
  return StructuredToolResult(
309
- status=ToolResultStatus.NO_DATA,
307
+ status=StructuredToolResultStatus.NO_DATA,
310
308
  params=params,
311
309
  data="No matching traces found.",
312
310
  )
313
311
 
314
312
  return StructuredToolResult(
315
- status=ToolResultStatus.SUCCESS,
313
+ status=StructuredToolResultStatus.SUCCESS,
316
314
  data=formatted_output,
317
315
  params=params,
318
316
  )
@@ -331,7 +329,7 @@ class FetchDatadogTracesList(BaseDatadogTracesTool):
331
329
  error_msg = f"Exception while querying Datadog: {str(e)}"
332
330
 
333
331
  return StructuredToolResult(
334
- status=ToolResultStatus.ERROR,
332
+ status=StructuredToolResultStatus.ERROR,
335
333
  error=error_msg,
336
334
  params=params,
337
335
  invocation=(
@@ -344,7 +342,7 @@ class FetchDatadogTracesList(BaseDatadogTracesTool):
344
342
  except Exception as e:
345
343
  logging.exception(e, exc_info=True)
346
344
  return StructuredToolResult(
347
- status=ToolResultStatus.ERROR,
345
+ status=StructuredToolResultStatus.ERROR,
348
346
  error=f"Unexpected error: {str(e)}",
349
347
  params=params,
350
348
  invocation=(
@@ -361,7 +359,7 @@ class FetchDatadogTraceById(BaseDatadogTracesTool):
361
359
  def __init__(self, toolset: "DatadogTracesToolset"):
362
360
  super().__init__(
363
361
  name="fetch_datadog_trace_by_id",
364
- description="Fetch detailed information about a specific trace by its ID",
362
+ description="[datadog/traces toolset] Fetch detailed information about a specific trace by its ID",
365
363
  parameters={
366
364
  "trace_id": ToolParameter(
367
365
  description="The trace ID to fetch details for",
@@ -377,13 +375,11 @@ class FetchDatadogTraceById(BaseDatadogTracesTool):
377
375
  trace_id = params.get("trace_id", "unknown")
378
376
  return f"{toolset_name_for_one_liner(self.toolset.name)}: Fetch Trace Details ({trace_id})"
379
377
 
380
- def _invoke(
381
- self, params: dict, user_approved: bool = False
382
- ) -> StructuredToolResult:
378
+ def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
383
379
  """Execute the tool to fetch trace details."""
384
380
  if not self.toolset.dd_config:
385
381
  return StructuredToolResult(
386
- status=ToolResultStatus.ERROR,
382
+ status=StructuredToolResultStatus.ERROR,
387
383
  error="Datadog configuration not initialized",
388
384
  params=params,
389
385
  )
@@ -391,7 +387,7 @@ class FetchDatadogTraceById(BaseDatadogTracesTool):
391
387
  trace_id = params.get("trace_id")
392
388
  if not trace_id:
393
389
  return StructuredToolResult(
394
- status=ToolResultStatus.ERROR,
390
+ status=StructuredToolResultStatus.ERROR,
395
391
  error="trace_id parameter is required",
396
392
  params=params,
397
393
  )
@@ -445,13 +441,13 @@ class FetchDatadogTraceById(BaseDatadogTracesTool):
445
441
  formatted_output = format_trace_hierarchy(trace_id, spans)
446
442
  if not formatted_output:
447
443
  return StructuredToolResult(
448
- status=ToolResultStatus.NO_DATA,
444
+ status=StructuredToolResultStatus.NO_DATA,
449
445
  params=params,
450
446
  data=f"No trace found for trace_id: {trace_id}",
451
447
  )
452
448
 
453
449
  return StructuredToolResult(
454
- status=ToolResultStatus.SUCCESS,
450
+ status=StructuredToolResultStatus.SUCCESS,
455
451
  data=formatted_output,
456
452
  params=params,
457
453
  )
@@ -470,7 +466,7 @@ class FetchDatadogTraceById(BaseDatadogTracesTool):
470
466
  error_msg = f"Exception while querying Datadog: {str(e)}"
471
467
 
472
468
  return StructuredToolResult(
473
- status=ToolResultStatus.ERROR,
469
+ status=StructuredToolResultStatus.ERROR,
474
470
  error=error_msg,
475
471
  params=params,
476
472
  invocation=(
@@ -483,7 +479,7 @@ class FetchDatadogTraceById(BaseDatadogTracesTool):
483
479
  except Exception as e:
484
480
  logging.exception(e, exc_info=True)
485
481
  return StructuredToolResult(
486
- status=ToolResultStatus.ERROR,
482
+ status=StructuredToolResultStatus.ERROR,
487
483
  error=f"Unexpected error: {str(e)}",
488
484
  params=params,
489
485
  invocation=(
@@ -500,7 +496,7 @@ class FetchDatadogSpansByFilter(BaseDatadogTracesTool):
500
496
  def __init__(self, toolset: "DatadogTracesToolset"):
501
497
  super().__init__(
502
498
  name="fetch_datadog_spans",
503
- description="Search for spans in Datadog with detailed filters",
499
+ description="[datadog/traces toolset] Search for spans in Datadog with detailed filters",
504
500
  parameters={
505
501
  "query": ToolParameter(
506
502
  description="Datadog search query (e.g., 'service:web-app @http.status_code:500')",
@@ -560,13 +556,11 @@ class FetchDatadogSpansByFilter(BaseDatadogTracesTool):
560
556
  filter_str = ", ".join(filters) if filters else "all"
561
557
  return f"{toolset_name_for_one_liner(self.toolset.name)}: Search Spans ({filter_str})"
562
558
 
563
- def _invoke(
564
- self, params: dict, user_approved: bool = False
565
- ) -> StructuredToolResult:
559
+ def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
566
560
  """Execute the tool to search spans."""
567
561
  if not self.toolset.dd_config:
568
562
  return StructuredToolResult(
569
- status=ToolResultStatus.ERROR,
563
+ status=StructuredToolResultStatus.ERROR,
570
564
  error="Datadog configuration not initialized",
571
565
  params=params,
572
566
  )
@@ -654,13 +648,13 @@ class FetchDatadogSpansByFilter(BaseDatadogTracesTool):
654
648
  formatted_output = format_spans_search(spans)
655
649
  if not formatted_output:
656
650
  return StructuredToolResult(
657
- status=ToolResultStatus.NO_DATA,
651
+ status=StructuredToolResultStatus.NO_DATA,
658
652
  params=params,
659
653
  data="No matching spans found.",
660
654
  )
661
655
 
662
656
  return StructuredToolResult(
663
- status=ToolResultStatus.SUCCESS,
657
+ status=StructuredToolResultStatus.SUCCESS,
664
658
  data=formatted_output,
665
659
  params=params,
666
660
  )
@@ -678,7 +672,7 @@ class FetchDatadogSpansByFilter(BaseDatadogTracesTool):
678
672
  error_msg = f"Exception while querying Datadog: {str(e)}"
679
673
 
680
674
  return StructuredToolResult(
681
- status=ToolResultStatus.ERROR,
675
+ status=StructuredToolResultStatus.ERROR,
682
676
  error=error_msg,
683
677
  params=params,
684
678
  invocation=(
@@ -691,7 +685,7 @@ class FetchDatadogSpansByFilter(BaseDatadogTracesTool):
691
685
  except Exception as e:
692
686
  logging.exception(e, exc_info=True)
693
687
  return StructuredToolResult(
694
- status=ToolResultStatus.ERROR,
688
+ status=StructuredToolResultStatus.ERROR,
695
689
  error=f"Unexpected error: {str(e)}",
696
690
  params=params,
697
691
  invocation=(
@@ -4,7 +4,11 @@ import requests # type: ignore
4
4
  import os
5
5
  from typing import Any, Optional, Dict, List, Tuple
6
6
  from pydantic import BaseModel
7
- from holmes.core.tools import StructuredToolResult, ToolResultStatus
7
+ from holmes.core.tools import (
8
+ StructuredToolResult,
9
+ StructuredToolResultStatus,
10
+ ToolInvokeContext,
11
+ )
8
12
 
9
13
  from holmes.core.tools import (
10
14
  Toolset,
@@ -20,10 +24,12 @@ class GitHubConfig(BaseModel):
20
24
  git_repo: str
21
25
  git_credentials: str
22
26
  git_branch: str = "main"
27
+ git_url: str = "https://api.github.com"
23
28
 
24
29
 
25
30
  class GitToolset(Toolset):
26
31
  git_repo: Optional[str] = None
32
+ git_url: Optional[str] = None
27
33
  git_credentials: Optional[str] = None
28
34
  git_branch: Optional[str] = None
29
35
  _created_branches: set[str] = set() # Track branches created by the tool
@@ -33,7 +39,7 @@ class GitToolset(Toolset):
33
39
  super().__init__(
34
40
  name="git",
35
41
  description="Runs git commands to read repos and create PRs",
36
- docs_url="https://docs.github.com/en/rest",
42
+ docs_url="https://holmesgpt.dev/data-sources/builtin-toolsets/github/",
37
43
  icon_url="https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg",
38
44
  prerequisites=[CallablePrerequisite(callable=self.prerequisites_callable)],
39
45
  tools=[
@@ -75,6 +81,7 @@ class GitToolset(Toolset):
75
81
 
76
82
  try:
77
83
  self.git_repo = os.getenv("GIT_REPO") or config.get("git_repo")
84
+ self.git_url = os.getenv("GIT_URL") or config.get("git_url")
78
85
  self.git_credentials = os.getenv("GIT_CREDENTIALS") or config.get(
79
86
  "git_credentials"
80
87
  )
@@ -82,7 +89,9 @@ class GitToolset(Toolset):
82
89
  "git_branch", "main"
83
90
  )
84
91
 
85
- if not all([self.git_repo, self.git_credentials, self.git_branch]):
92
+ if not all(
93
+ [self.git_repo, self.git_url, self.git_credentials, self.git_branch]
94
+ ):
86
95
  logging.error("Missing one or more required Git configuration values.")
87
96
  return False, "Missing one or more required Git configuration values."
88
97
  return True, ""
@@ -96,7 +105,7 @@ class GitToolset(Toolset):
96
105
  def list_open_prs(self) -> List[Dict[str, Any]]:
97
106
  """Helper method to list all open PRs in the repository."""
98
107
  headers = {"Authorization": f"token {self.git_credentials}"}
99
- url = f"https://api.github.com/repos/{self.git_repo}/pulls?state=open"
108
+ url = f"{self.git_url}/repos/{self.git_repo}/pulls?state=open"
100
109
  resp = requests.get(url, headers=headers)
101
110
  if resp.status_code != 200:
102
111
  raise Exception(self._sanitize_error(f"Error listing PRs: {resp.text}"))
@@ -105,9 +114,7 @@ class GitToolset(Toolset):
105
114
  def get_branch_ref(self, branch_name: str) -> Optional[str]:
106
115
  """Get the SHA of a branch reference."""
107
116
  headers = {"Authorization": f"token {self.git_credentials}"}
108
- url = (
109
- f"https://api.github.com/repos/{self.git_repo}/git/refs/heads/{branch_name}"
110
- )
117
+ url = f"{self.git_url}/repos/{self.git_repo}/git/refs/heads/{branch_name}"
111
118
  resp = requests.get(url, headers=headers)
112
119
  if resp.status_code == 404:
113
120
  return None
@@ -120,7 +127,7 @@ class GitToolset(Toolset):
120
127
  def create_branch(self, branch_name: str, base_sha: str) -> None:
121
128
  """Create a new branch from a base SHA."""
122
129
  headers = {"Authorization": f"token {self.git_credentials}"}
123
- url = f"https://api.github.com/repos/{self.git_repo}/git/refs"
130
+ url = f"{self.git_url}/repos/{self.git_repo}/git/refs"
124
131
  resp = requests.post(
125
132
  url,
126
133
  headers=headers,
@@ -136,7 +143,7 @@ class GitToolset(Toolset):
136
143
  def get_file_content(self, filepath: str, branch: str) -> tuple[str, str]:
137
144
  """Get file content and SHA from a specific branch."""
138
145
  headers = {"Authorization": f"token {self.git_credentials}"}
139
- url = f"https://api.github.com/repos/{self.git_repo}/contents/{filepath}?ref={branch}"
146
+ url = f"{self.git_url}/repos/{self.git_repo}/contents/{filepath}?ref={branch}"
140
147
  resp = requests.get(url, headers=headers)
141
148
  if resp.status_code == 404:
142
149
  raise Exception(f"File not found: {filepath}")
@@ -150,7 +157,7 @@ class GitToolset(Toolset):
150
157
  ) -> None:
151
158
  """Update a file in a specific branch."""
152
159
  headers = {"Authorization": f"token {self.git_credentials}"}
153
- url = f"https://api.github.com/repos/{self.git_repo}/contents/{filepath}"
160
+ url = f"{self.git_url}/repos/{self.git_repo}/contents/{filepath}"
154
161
  encoded_content = base64.b64encode(content.encode()).decode()
155
162
  resp = requests.put(
156
163
  url,
@@ -168,7 +175,7 @@ class GitToolset(Toolset):
168
175
  def create_pr(self, title: str, head: str, base: str, body: str) -> str:
169
176
  """Create a new pull request."""
170
177
  headers = {"Authorization": f"token {self.git_credentials}"}
171
- url = f"https://api.github.com/repos/{self.git_repo}/pulls"
178
+ url = f"{self.git_url}/repos/{self.git_repo}/pulls"
172
179
  resp = requests.post(
173
180
  url,
174
181
  headers=headers,
@@ -188,7 +195,7 @@ class GitToolset(Toolset):
188
195
  def get_pr_details(self, pr_number: int) -> Dict[str, Any]:
189
196
  """Get details of a specific PR."""
190
197
  headers = {"Authorization": f"token {self.git_credentials}"}
191
- url = f"https://api.github.com/repos/{self.git_repo}/pulls/{pr_number}"
198
+ url = f"{self.git_url}/repos/{self.git_repo}/pulls/{pr_number}"
192
199
  resp = requests.get(url, headers=headers)
193
200
  if resp.status_code != 200:
194
201
  raise Exception(
@@ -215,7 +222,7 @@ class GitToolset(Toolset):
215
222
 
216
223
  # Update file
217
224
  headers = {"Authorization": f"token {self.git_credentials}"}
218
- url = f"https://api.github.com/repos/{self.git_repo}/contents/{filepath}"
225
+ url = f"{self.git_url}/repos/{self.git_repo}/contents/{filepath}"
219
226
  encoded_content = base64.b64encode(content.encode()).decode()
220
227
  data = {
221
228
  "message": message,
@@ -250,16 +257,18 @@ class GitReadFileWithLineNumbers(Tool):
250
257
  )
251
258
 
252
259
  def _invoke(
253
- self, params: dict, user_approved: bool = False
260
+ self,
261
+ params: dict,
262
+ context: ToolInvokeContext,
254
263
  ) -> StructuredToolResult:
255
264
  filepath = params["filepath"]
256
265
  try:
257
266
  headers = {"Authorization": f"token {self.toolset.git_credentials}"}
258
- url = f"https://api.github.com/repos/{self.toolset.git_repo}/contents/{filepath}"
267
+ url = f"{self.toolset.git_url}/repos/{self.toolset.git_repo}/contents/{filepath}"
259
268
  resp = requests.get(url, headers=headers)
260
269
  if resp.status_code != 200:
261
270
  return StructuredToolResult(
262
- status=ToolResultStatus.ERROR,
271
+ status=StructuredToolResultStatus.ERROR,
263
272
  data=self.toolset._sanitize_error(
264
273
  f"Error fetching file: {resp.text}"
265
274
  ),
@@ -268,13 +277,13 @@ class GitReadFileWithLineNumbers(Tool):
268
277
  content = base64.b64decode(resp.json()["content"]).decode().splitlines()
269
278
  numbered = "\n".join(f"{i+1}: {line}" for i, line in enumerate(content))
270
279
  return StructuredToolResult(
271
- status=ToolResultStatus.SUCCESS,
280
+ status=StructuredToolResultStatus.SUCCESS,
272
281
  data=numbered,
273
282
  params=params,
274
283
  )
275
284
  except Exception as e:
276
285
  return StructuredToolResult(
277
- status=ToolResultStatus.ERROR,
286
+ status=StructuredToolResultStatus.ERROR,
278
287
  data=self.toolset._sanitize_error(str(e)),
279
288
  params=params,
280
289
  )
@@ -296,15 +305,17 @@ class GitListFiles(Tool):
296
305
  )
297
306
 
298
307
  def _invoke(
299
- self, params: dict, user_approved: bool = False
308
+ self,
309
+ params: dict,
310
+ context: ToolInvokeContext,
300
311
  ) -> StructuredToolResult:
301
312
  try:
302
313
  headers = {"Authorization": f"token {self.toolset.git_credentials}"}
303
- url = f"https://api.github.com/repos/{self.toolset.git_repo}/git/trees/{self.toolset.git_branch}?recursive=1"
314
+ url = f"{self.toolset.git_url}/repos/{self.toolset.git_repo}/git/trees/{self.toolset.git_branch}?recursive=1"
304
315
  resp = requests.get(url, headers=headers)
305
316
  if resp.status_code != 200:
306
317
  return StructuredToolResult(
307
- status=ToolResultStatus.ERROR,
318
+ status=StructuredToolResultStatus.ERROR,
308
319
  data=self.toolset._sanitize_error(
309
320
  f"Error listing files: {resp.text}"
310
321
  ),
@@ -312,13 +323,13 @@ class GitListFiles(Tool):
312
323
  )
313
324
  paths = [entry["path"] for entry in resp.json()["tree"]]
314
325
  return StructuredToolResult(
315
- status=ToolResultStatus.SUCCESS,
326
+ status=StructuredToolResultStatus.SUCCESS,
316
327
  data=paths,
317
328
  params=params,
318
329
  )
319
330
  except Exception as e:
320
331
  return StructuredToolResult(
321
- status=ToolResultStatus.ERROR,
332
+ status=StructuredToolResultStatus.ERROR,
322
333
  data=self.toolset._sanitize_error(str(e)),
323
334
  params=params,
324
335
  )
@@ -338,9 +349,7 @@ class GitListOpenPRs(Tool):
338
349
  toolset=toolset, # type: ignore
339
350
  )
340
351
 
341
- def _invoke(
342
- self, params: dict, user_approved: bool = False
343
- ) -> StructuredToolResult:
352
+ def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
344
353
  try:
345
354
  prs = self.toolset.list_open_prs()
346
355
  formatted = [
@@ -353,13 +362,13 @@ class GitListOpenPRs(Tool):
353
362
  for pr in prs
354
363
  ]
355
364
  return StructuredToolResult(
356
- status=ToolResultStatus.SUCCESS,
365
+ status=StructuredToolResultStatus.SUCCESS,
357
366
  data=formatted,
358
367
  params=params,
359
368
  )
360
369
  except Exception as e:
361
370
  return StructuredToolResult(
362
- status=ToolResultStatus.ERROR,
371
+ status=StructuredToolResultStatus.ERROR,
363
372
  data=self.toolset._sanitize_error(str(e)),
364
373
  params=params,
365
374
  )
@@ -408,19 +417,17 @@ class GitExecuteChanges(Tool):
408
417
  toolset=toolset, # type: ignore
409
418
  )
410
419
 
411
- def _invoke(
412
- self, params: dict, user_approved: bool = False
413
- ) -> StructuredToolResult:
420
+ def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
414
421
  def error(msg: str) -> StructuredToolResult:
415
422
  return StructuredToolResult(
416
- status=ToolResultStatus.ERROR,
423
+ status=StructuredToolResultStatus.ERROR,
417
424
  data=self.toolset._sanitize_error(msg),
418
425
  params=params,
419
426
  )
420
427
 
421
428
  def success(msg: Any) -> StructuredToolResult:
422
429
  return StructuredToolResult(
423
- status=ToolResultStatus.SUCCESS, data=msg, params=params
430
+ status=StructuredToolResultStatus.SUCCESS, data=msg, params=params
424
431
  )
425
432
 
426
433
  def modify_lines(lines: List[str]) -> List[str]:
@@ -628,9 +635,7 @@ class GitUpdatePR(Tool):
628
635
  toolset=toolset, # type: ignore
629
636
  )
630
637
 
631
- def _invoke(
632
- self, params: dict, user_approved: bool = False
633
- ) -> StructuredToolResult:
638
+ def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
634
639
  try:
635
640
  line = params["line"]
636
641
  filename = params["filename"]
@@ -643,24 +648,24 @@ class GitUpdatePR(Tool):
643
648
  # Validate inputs
644
649
  if not commit_message.strip():
645
650
  return StructuredToolResult(
646
- status=ToolResultStatus.ERROR,
651
+ status=StructuredToolResultStatus.ERROR,
647
652
  error="Tool call failed to run: Commit message cannot be empty",
648
653
  )
649
654
  if not filename.strip():
650
655
  return StructuredToolResult(
651
- status=ToolResultStatus.ERROR,
656
+ status=StructuredToolResultStatus.ERROR,
652
657
  error="Tool call failed to run: Filename cannot be empty",
653
658
  )
654
659
  if line < 1:
655
660
  return StructuredToolResult(
656
- status=ToolResultStatus.ERROR,
661
+ status=StructuredToolResultStatus.ERROR,
657
662
  error="Tool call failed to run: Line number must be positive",
658
663
  )
659
664
 
660
665
  # Verify this is a PR created by our tool
661
666
  if not self.toolset.is_created_pr(pr_number):
662
667
  return StructuredToolResult(
663
- status=ToolResultStatus.ERROR,
668
+ status=StructuredToolResultStatus.ERROR,
664
669
  error=f"Tool call failed to run: PR #{pr_number} was not created by this tool. Only PRs created using git_execute_changes can be updated.",
665
670
  )
666
671
 
@@ -714,7 +719,7 @@ class GitUpdatePR(Tool):
714
719
  del content_lines[line - 1]
715
720
  else:
716
721
  return StructuredToolResult(
717
- status=ToolResultStatus.ERROR,
722
+ status=StructuredToolResultStatus.ERROR,
718
723
  error=f"Tool call failed to run: Invalid command: {command}",
719
724
  )
720
725
 
@@ -722,7 +727,7 @@ class GitUpdatePR(Tool):
722
727
 
723
728
  if dry_run:
724
729
  return StructuredToolResult(
725
- status=ToolResultStatus.SUCCESS,
730
+ status=StructuredToolResultStatus.SUCCESS,
726
731
  data=f"DRY RUN: Updated content for PR #{pr_number}:\n\n{updated_content}",
727
732
  )
728
733
 
@@ -731,13 +736,13 @@ class GitUpdatePR(Tool):
731
736
  pr_number, filename, updated_content, commit_message
732
737
  )
733
738
  return StructuredToolResult(
734
- status=ToolResultStatus.SUCCESS,
739
+ status=StructuredToolResultStatus.SUCCESS,
735
740
  data=f"Added commit to PR #{pr_number} successfully",
736
741
  )
737
742
 
738
743
  except Exception as e:
739
744
  return StructuredToolResult(
740
- status=ToolResultStatus.ERROR,
745
+ status=StructuredToolResultStatus.ERROR,
741
746
  error=self.toolset._sanitize_error(
742
747
  f"Tool call failed to run: Error updating PR: {str(e)}"
743
748
  ),
@@ -745,14 +750,14 @@ class GitUpdatePR(Tool):
745
750
 
746
751
  except requests.exceptions.RequestException as e:
747
752
  return StructuredToolResult(
748
- status=ToolResultStatus.ERROR,
753
+ status=StructuredToolResultStatus.ERROR,
749
754
  error=self.toolset._sanitize_error(
750
755
  f"Tool call failed to run: Network error: {str(e)}"
751
756
  ),
752
757
  )
753
758
  except Exception as e:
754
759
  return StructuredToolResult(
755
- status=ToolResultStatus.ERROR,
760
+ status=StructuredToolResultStatus.ERROR,
756
761
  error=self.toolset._sanitize_error(
757
762
  f"Tool call failed to run: Unexpected error: {str(e)}"
758
763
  ),