kubiya-control-plane-api 0.1.0__py3-none-any.whl → 0.3.4__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 kubiya-control-plane-api might be problematic. Click here for more details.

Files changed (185) hide show
  1. control_plane_api/README.md +266 -0
  2. control_plane_api/__init__.py +0 -0
  3. control_plane_api/__version__.py +1 -0
  4. control_plane_api/alembic/README +1 -0
  5. control_plane_api/alembic/env.py +98 -0
  6. control_plane_api/alembic/script.py.mako +28 -0
  7. control_plane_api/alembic/versions/1382bec74309_initial_migration_with_all_models.py +251 -0
  8. control_plane_api/alembic/versions/1f54bc2a37e3_add_analytics_tables.py +162 -0
  9. control_plane_api/alembic/versions/2e4cb136dc10_rename_toolset_ids_to_skill_ids_in_teams.py +30 -0
  10. control_plane_api/alembic/versions/31cd69a644ce_add_skill_templates_table.py +28 -0
  11. control_plane_api/alembic/versions/89e127caa47d_add_jobs_and_job_executions_tables.py +161 -0
  12. control_plane_api/alembic/versions/add_llm_models_table.py +51 -0
  13. control_plane_api/alembic/versions/b0e10697f212_add_runtime_column_to_teams_simple.py +42 -0
  14. control_plane_api/alembic/versions/ce43b24b63bf_add_execution_trigger_source_and_fix_.py +155 -0
  15. control_plane_api/alembic/versions/d4eaf16e3f8d_rename_toolsets_to_skills.py +84 -0
  16. control_plane_api/alembic/versions/efa2dc427da1_rename_metadata_to_custom_metadata.py +32 -0
  17. control_plane_api/alembic/versions/f973b431d1ce_add_workflow_executor_to_skill_types.py +44 -0
  18. control_plane_api/alembic.ini +148 -0
  19. control_plane_api/api/index.py +12 -0
  20. control_plane_api/app/__init__.py +11 -0
  21. control_plane_api/app/activities/__init__.py +20 -0
  22. control_plane_api/app/activities/agent_activities.py +379 -0
  23. control_plane_api/app/activities/team_activities.py +410 -0
  24. control_plane_api/app/activities/temporal_cloud_activities.py +577 -0
  25. control_plane_api/app/config/__init__.py +35 -0
  26. control_plane_api/app/config/api_config.py +354 -0
  27. control_plane_api/app/config/model_pricing.py +318 -0
  28. control_plane_api/app/config.py +95 -0
  29. control_plane_api/app/database.py +135 -0
  30. control_plane_api/app/exceptions.py +408 -0
  31. control_plane_api/app/lib/__init__.py +11 -0
  32. control_plane_api/app/lib/job_executor.py +312 -0
  33. control_plane_api/app/lib/kubiya_client.py +235 -0
  34. control_plane_api/app/lib/litellm_pricing.py +166 -0
  35. control_plane_api/app/lib/planning_tools/__init__.py +22 -0
  36. control_plane_api/app/lib/planning_tools/agents.py +155 -0
  37. control_plane_api/app/lib/planning_tools/base.py +189 -0
  38. control_plane_api/app/lib/planning_tools/environments.py +214 -0
  39. control_plane_api/app/lib/planning_tools/resources.py +240 -0
  40. control_plane_api/app/lib/planning_tools/teams.py +198 -0
  41. control_plane_api/app/lib/policy_enforcer_client.py +939 -0
  42. control_plane_api/app/lib/redis_client.py +436 -0
  43. control_plane_api/app/lib/supabase.py +71 -0
  44. control_plane_api/app/lib/temporal_client.py +138 -0
  45. control_plane_api/app/lib/validation/__init__.py +20 -0
  46. control_plane_api/app/lib/validation/runtime_validation.py +287 -0
  47. control_plane_api/app/main.py +128 -0
  48. control_plane_api/app/middleware/__init__.py +8 -0
  49. control_plane_api/app/middleware/auth.py +513 -0
  50. control_plane_api/app/middleware/exception_handler.py +267 -0
  51. control_plane_api/app/middleware/rate_limiting.py +384 -0
  52. control_plane_api/app/middleware/request_id.py +202 -0
  53. control_plane_api/app/models/__init__.py +27 -0
  54. control_plane_api/app/models/agent.py +79 -0
  55. control_plane_api/app/models/analytics.py +206 -0
  56. control_plane_api/app/models/associations.py +81 -0
  57. control_plane_api/app/models/environment.py +63 -0
  58. control_plane_api/app/models/execution.py +93 -0
  59. control_plane_api/app/models/job.py +179 -0
  60. control_plane_api/app/models/llm_model.py +75 -0
  61. control_plane_api/app/models/presence.py +49 -0
  62. control_plane_api/app/models/project.py +47 -0
  63. control_plane_api/app/models/session.py +38 -0
  64. control_plane_api/app/models/team.py +66 -0
  65. control_plane_api/app/models/workflow.py +55 -0
  66. control_plane_api/app/policies/README.md +121 -0
  67. control_plane_api/app/policies/approved_users.rego +62 -0
  68. control_plane_api/app/policies/business_hours.rego +51 -0
  69. control_plane_api/app/policies/rate_limiting.rego +100 -0
  70. control_plane_api/app/policies/tool_restrictions.rego +86 -0
  71. control_plane_api/app/routers/__init__.py +4 -0
  72. control_plane_api/app/routers/agents.py +364 -0
  73. control_plane_api/app/routers/agents_v2.py +1260 -0
  74. control_plane_api/app/routers/analytics.py +1014 -0
  75. control_plane_api/app/routers/context_manager.py +562 -0
  76. control_plane_api/app/routers/environment_context.py +270 -0
  77. control_plane_api/app/routers/environments.py +715 -0
  78. control_plane_api/app/routers/execution_environment.py +517 -0
  79. control_plane_api/app/routers/executions.py +1911 -0
  80. control_plane_api/app/routers/health.py +92 -0
  81. control_plane_api/app/routers/health_v2.py +326 -0
  82. control_plane_api/app/routers/integrations.py +274 -0
  83. control_plane_api/app/routers/jobs.py +1344 -0
  84. control_plane_api/app/routers/models.py +82 -0
  85. control_plane_api/app/routers/models_v2.py +361 -0
  86. control_plane_api/app/routers/policies.py +639 -0
  87. control_plane_api/app/routers/presence.py +234 -0
  88. control_plane_api/app/routers/projects.py +902 -0
  89. control_plane_api/app/routers/runners.py +379 -0
  90. control_plane_api/app/routers/runtimes.py +172 -0
  91. control_plane_api/app/routers/secrets.py +155 -0
  92. control_plane_api/app/routers/skills.py +1001 -0
  93. control_plane_api/app/routers/skills_definitions.py +140 -0
  94. control_plane_api/app/routers/task_planning.py +1256 -0
  95. control_plane_api/app/routers/task_queues.py +654 -0
  96. control_plane_api/app/routers/team_context.py +270 -0
  97. control_plane_api/app/routers/teams.py +1400 -0
  98. control_plane_api/app/routers/worker_queues.py +1545 -0
  99. control_plane_api/app/routers/workers.py +935 -0
  100. control_plane_api/app/routers/workflows.py +204 -0
  101. control_plane_api/app/runtimes/__init__.py +6 -0
  102. control_plane_api/app/runtimes/validation.py +344 -0
  103. control_plane_api/app/schemas/job_schemas.py +295 -0
  104. control_plane_api/app/services/__init__.py +1 -0
  105. control_plane_api/app/services/agno_service.py +619 -0
  106. control_plane_api/app/services/litellm_service.py +190 -0
  107. control_plane_api/app/services/policy_service.py +525 -0
  108. control_plane_api/app/services/temporal_cloud_provisioning.py +150 -0
  109. control_plane_api/app/skills/__init__.py +44 -0
  110. control_plane_api/app/skills/base.py +229 -0
  111. control_plane_api/app/skills/business_intelligence.py +189 -0
  112. control_plane_api/app/skills/data_visualization.py +154 -0
  113. control_plane_api/app/skills/docker.py +104 -0
  114. control_plane_api/app/skills/file_generation.py +94 -0
  115. control_plane_api/app/skills/file_system.py +110 -0
  116. control_plane_api/app/skills/python.py +92 -0
  117. control_plane_api/app/skills/registry.py +65 -0
  118. control_plane_api/app/skills/shell.py +102 -0
  119. control_plane_api/app/skills/workflow_executor.py +469 -0
  120. control_plane_api/app/utils/workflow_executor.py +354 -0
  121. control_plane_api/app/workflows/__init__.py +11 -0
  122. control_plane_api/app/workflows/agent_execution.py +507 -0
  123. control_plane_api/app/workflows/agent_execution_with_skills.py +222 -0
  124. control_plane_api/app/workflows/namespace_provisioning.py +326 -0
  125. control_plane_api/app/workflows/team_execution.py +399 -0
  126. control_plane_api/scripts/seed_models.py +239 -0
  127. control_plane_api/worker/__init__.py +0 -0
  128. control_plane_api/worker/activities/__init__.py +0 -0
  129. control_plane_api/worker/activities/agent_activities.py +1241 -0
  130. control_plane_api/worker/activities/approval_activities.py +234 -0
  131. control_plane_api/worker/activities/runtime_activities.py +388 -0
  132. control_plane_api/worker/activities/skill_activities.py +267 -0
  133. control_plane_api/worker/activities/team_activities.py +1217 -0
  134. control_plane_api/worker/config/__init__.py +31 -0
  135. control_plane_api/worker/config/worker_config.py +275 -0
  136. control_plane_api/worker/control_plane_client.py +529 -0
  137. control_plane_api/worker/examples/analytics_integration_example.py +362 -0
  138. control_plane_api/worker/models/__init__.py +1 -0
  139. control_plane_api/worker/models/inputs.py +89 -0
  140. control_plane_api/worker/runtimes/__init__.py +31 -0
  141. control_plane_api/worker/runtimes/base.py +789 -0
  142. control_plane_api/worker/runtimes/claude_code_runtime.py +1443 -0
  143. control_plane_api/worker/runtimes/default_runtime.py +617 -0
  144. control_plane_api/worker/runtimes/factory.py +173 -0
  145. control_plane_api/worker/runtimes/validation.py +93 -0
  146. control_plane_api/worker/services/__init__.py +1 -0
  147. control_plane_api/worker/services/agent_executor.py +422 -0
  148. control_plane_api/worker/services/agent_executor_v2.py +383 -0
  149. control_plane_api/worker/services/analytics_collector.py +457 -0
  150. control_plane_api/worker/services/analytics_service.py +464 -0
  151. control_plane_api/worker/services/approval_tools.py +310 -0
  152. control_plane_api/worker/services/approval_tools_agno.py +207 -0
  153. control_plane_api/worker/services/cancellation_manager.py +177 -0
  154. control_plane_api/worker/services/data_visualization.py +827 -0
  155. control_plane_api/worker/services/jira_tools.py +257 -0
  156. control_plane_api/worker/services/runtime_analytics.py +328 -0
  157. control_plane_api/worker/services/session_service.py +194 -0
  158. control_plane_api/worker/services/skill_factory.py +175 -0
  159. control_plane_api/worker/services/team_executor.py +574 -0
  160. control_plane_api/worker/services/team_executor_v2.py +465 -0
  161. control_plane_api/worker/services/workflow_executor_tools.py +1418 -0
  162. control_plane_api/worker/tests/__init__.py +1 -0
  163. control_plane_api/worker/tests/e2e/__init__.py +0 -0
  164. control_plane_api/worker/tests/e2e/test_execution_flow.py +571 -0
  165. control_plane_api/worker/tests/integration/__init__.py +0 -0
  166. control_plane_api/worker/tests/integration/test_control_plane_integration.py +308 -0
  167. control_plane_api/worker/tests/unit/__init__.py +0 -0
  168. control_plane_api/worker/tests/unit/test_control_plane_client.py +401 -0
  169. control_plane_api/worker/utils/__init__.py +1 -0
  170. control_plane_api/worker/utils/chunk_batcher.py +305 -0
  171. control_plane_api/worker/utils/retry_utils.py +60 -0
  172. control_plane_api/worker/utils/streaming_utils.py +373 -0
  173. control_plane_api/worker/worker.py +753 -0
  174. control_plane_api/worker/workflows/__init__.py +0 -0
  175. control_plane_api/worker/workflows/agent_execution.py +589 -0
  176. control_plane_api/worker/workflows/team_execution.py +429 -0
  177. kubiya_control_plane_api-0.3.4.dist-info/METADATA +229 -0
  178. kubiya_control_plane_api-0.3.4.dist-info/RECORD +182 -0
  179. kubiya_control_plane_api-0.3.4.dist-info/entry_points.txt +2 -0
  180. kubiya_control_plane_api-0.3.4.dist-info/top_level.txt +1 -0
  181. kubiya_control_plane_api-0.1.0.dist-info/METADATA +0 -66
  182. kubiya_control_plane_api-0.1.0.dist-info/RECORD +0 -5
  183. kubiya_control_plane_api-0.1.0.dist-info/top_level.txt +0 -1
  184. {kubiya_control_plane_api-0.1.0.dist-info/licenses → control_plane_api}/LICENSE +0 -0
  185. {kubiya_control_plane_api-0.1.0.dist-info → kubiya_control_plane_api-0.3.4.dist-info}/WHEEL +0 -0
@@ -0,0 +1,827 @@
1
+ """
2
+ Data Visualization Tools for Agent Control Plane Worker
3
+
4
+ This module provides tools for agents to create diagrams and visualizations
5
+ using Mermaid syntax. The tools emit special events that the UI can parse
6
+ and render as interactive diagrams.
7
+
8
+ Event Format:
9
+ The tool emits events in the following format:
10
+
11
+ <<DIAGRAM_START>>
12
+ ```mermaid
13
+ [mermaid diagram code]
14
+ ```
15
+ <<DIAGRAM_END>>
16
+
17
+ This format allows the UI to:
18
+ 1. Detect diagram events via <<DIAGRAM_START>> and <<DIAGRAM_END>> markers
19
+ 2. Extract the Mermaid syntax between the markers
20
+ 3. Render the diagram using a Mermaid renderer component
21
+ """
22
+
23
+ import json
24
+ import re
25
+ from typing import Optional, Callable, Any, Dict, List
26
+ from agno.tools import Toolkit
27
+ import logging
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ # Mermaid syntax validation patterns
33
+ MERMAID_DIAGRAM_TYPES = {
34
+ 'flowchart': r'^flowchart\s+(TD|TB|BT|RL|LR)',
35
+ 'graph': r'^graph\s+(TD|TB|BT|RL|LR)',
36
+ 'sequenceDiagram': r'^sequenceDiagram',
37
+ 'classDiagram': r'^classDiagram',
38
+ 'stateDiagram': r'^stateDiagram(-v2)?',
39
+ 'erDiagram': r'^erDiagram',
40
+ 'journey': r'^journey',
41
+ 'gantt': r'^gantt',
42
+ 'pie': r'^pie',
43
+ 'gitGraph': r'^gitGraph',
44
+ 'mindmap': r'^mindmap',
45
+ 'timeline': r'^timeline',
46
+ 'quadrantChart': r'^quadrantChart',
47
+ }
48
+
49
+ # Forbidden patterns that could cause rendering issues
50
+ FORBIDDEN_PATTERNS = [
51
+ r'<script', # No script tags
52
+ r'javascript:', # No javascript: URLs
53
+ r'on\w+\s*=', # No event handlers
54
+ r'eval\(', # No eval
55
+ r'\.innerHTML', # No innerHTML manipulation
56
+ ]
57
+
58
+
59
+ class MermaidValidator:
60
+ """Validates Mermaid diagram syntax to ensure safe and correct rendering."""
61
+
62
+ @staticmethod
63
+ def validate_syntax(diagram_code: str, expected_type: Optional[str] = None) -> tuple[bool, Optional[str]]:
64
+ """
65
+ Validate Mermaid diagram syntax.
66
+
67
+ Args:
68
+ diagram_code: The Mermaid diagram code to validate
69
+ expected_type: Optional expected diagram type (e.g., 'flowchart', 'sequenceDiagram')
70
+
71
+ Returns:
72
+ Tuple of (is_valid, error_message)
73
+ """
74
+ if not diagram_code or not diagram_code.strip():
75
+ return False, "Diagram code is empty"
76
+
77
+ # Check for forbidden patterns
78
+ for pattern in FORBIDDEN_PATTERNS:
79
+ if re.search(pattern, diagram_code, re.IGNORECASE):
80
+ return False, f"Forbidden pattern detected: {pattern}"
81
+
82
+ # Check for valid diagram type
83
+ diagram_lines = [line.strip() for line in diagram_code.strip().split('\n') if line.strip()]
84
+ if not diagram_lines:
85
+ return False, "No content in diagram"
86
+
87
+ # Skip metadata lines (---...---)
88
+ first_code_line = None
89
+ in_metadata = False
90
+ for line in diagram_lines:
91
+ if line == '---':
92
+ in_metadata = not in_metadata
93
+ continue
94
+ if not in_metadata and line:
95
+ first_code_line = line
96
+ break
97
+
98
+ if not first_code_line:
99
+ return False, "No diagram definition found"
100
+
101
+ # Validate diagram type
102
+ found_valid_type = False
103
+ detected_type = None
104
+ for diagram_type, pattern in MERMAID_DIAGRAM_TYPES.items():
105
+ if re.match(pattern, first_code_line, re.IGNORECASE):
106
+ found_valid_type = True
107
+ detected_type = diagram_type
108
+ break
109
+
110
+ if not found_valid_type:
111
+ return False, f"Invalid diagram type. First line: {first_code_line[:50]}"
112
+
113
+ # If expected type provided, verify it matches
114
+ if expected_type and detected_type:
115
+ if expected_type.lower() not in detected_type.lower():
116
+ return False, f"Expected {expected_type} but detected {detected_type}"
117
+
118
+ # Check for balanced brackets/parentheses (basic structural validation)
119
+ open_chars = {'(': 0, '[': 0, '{': 0}
120
+ close_chars = {')': '(', ']': '[', '}': '{'}
121
+ stack = []
122
+
123
+ for char in diagram_code:
124
+ if char in open_chars:
125
+ stack.append(char)
126
+ elif char in close_chars:
127
+ if not stack or stack[-1] != close_chars[char]:
128
+ # Don't fail on unbalanced - just warn
129
+ logger.warning(f"Potentially unbalanced brackets in diagram")
130
+ elif stack:
131
+ stack.pop()
132
+
133
+ return True, None
134
+
135
+ @staticmethod
136
+ def sanitize_content(content: str) -> str:
137
+ """
138
+ Sanitize diagram content to remove potentially problematic characters.
139
+
140
+ Args:
141
+ content: Content to sanitize
142
+
143
+ Returns:
144
+ Sanitized content
145
+ """
146
+ # Remove null bytes
147
+ content = content.replace('\x00', '')
148
+
149
+ # Normalize line endings
150
+ content = content.replace('\r\n', '\n').replace('\r', '\n')
151
+
152
+ # Remove excessive whitespace while preserving structure
153
+ lines = content.split('\n')
154
+ cleaned_lines = []
155
+ for line in lines:
156
+ # Preserve indentation but trim trailing spaces
157
+ cleaned_line = line.rstrip()
158
+ cleaned_lines.append(cleaned_line)
159
+
160
+ return '\n'.join(cleaned_lines)
161
+
162
+
163
+ class DataVisualizationTools(Toolkit):
164
+ """
165
+ Data Visualization toolkit for creating diagrams using Mermaid syntax.
166
+
167
+ Agents can use these tools to create various types of diagrams for:
168
+ - Data analysis and BI intelligence
169
+ - System architecture visualization
170
+ - Process flows and workflows
171
+ - Database schemas and ER diagrams
172
+ - Project timelines and Gantt charts
173
+ """
174
+
175
+ def __init__(
176
+ self,
177
+ max_diagram_size: int = 50000,
178
+ enable_flowchart: bool = True,
179
+ enable_sequence: bool = True,
180
+ enable_class_diagram: bool = True,
181
+ enable_er_diagram: bool = True,
182
+ enable_gantt: bool = True,
183
+ enable_pie_chart: bool = True,
184
+ enable_state_diagram: bool = True,
185
+ enable_git_graph: bool = True,
186
+ enable_user_journey: bool = True,
187
+ enable_quadrant_chart: bool = True,
188
+ stream_callback: Optional[Callable[[str], None]] = None,
189
+ ):
190
+ """
191
+ Initialize DataVisualizationTools.
192
+
193
+ Args:
194
+ max_diagram_size: Maximum size of diagram in characters
195
+ enable_*: Enable/disable specific diagram types
196
+ stream_callback: Optional callback for streaming output
197
+ """
198
+ super().__init__(name="data_visualization")
199
+
200
+ self.max_diagram_size = max_diagram_size
201
+ self.enable_flowchart = enable_flowchart
202
+ self.enable_sequence = enable_sequence
203
+ self.enable_class_diagram = enable_class_diagram
204
+ self.enable_er_diagram = enable_er_diagram
205
+ self.enable_gantt = enable_gantt
206
+ self.enable_pie_chart = enable_pie_chart
207
+ self.enable_state_diagram = enable_state_diagram
208
+ self.enable_git_graph = enable_git_graph
209
+ self.enable_user_journey = enable_user_journey
210
+ self.enable_quadrant_chart = enable_quadrant_chart
211
+ self.stream_callback = stream_callback
212
+
213
+ # Register all enabled tools
214
+ if enable_flowchart:
215
+ self.register(self.create_flowchart)
216
+ if enable_sequence:
217
+ self.register(self.create_sequence_diagram)
218
+ if enable_class_diagram:
219
+ self.register(self.create_class_diagram)
220
+ if enable_er_diagram:
221
+ self.register(self.create_er_diagram)
222
+ if enable_gantt:
223
+ self.register(self.create_gantt_chart)
224
+ if enable_pie_chart:
225
+ self.register(self.create_pie_chart)
226
+ if enable_state_diagram:
227
+ self.register(self.create_state_diagram)
228
+ if enable_git_graph:
229
+ self.register(self.create_git_graph)
230
+ if enable_user_journey:
231
+ self.register(self.create_user_journey)
232
+ if enable_quadrant_chart:
233
+ self.register(self.create_quadrant_chart)
234
+
235
+ # Generic create_diagram tool
236
+ self.register(self.create_diagram)
237
+
238
+ def _emit_diagram(
239
+ self,
240
+ diagram_code: str,
241
+ diagram_type: str,
242
+ validate: bool = True,
243
+ auto_fix: bool = True
244
+ ) -> str:
245
+ """
246
+ Emit a diagram event with proper formatting for UI parsing.
247
+ Includes validation, sanitization, and error handling.
248
+
249
+ Args:
250
+ diagram_code: Mermaid diagram code
251
+ diagram_type: Type of diagram (for logging/metadata)
252
+ validate: Whether to validate the diagram syntax (default: True)
253
+ auto_fix: Whether to attempt auto-fixing common issues (default: True)
254
+
255
+ Returns:
256
+ Formatted diagram event string or error message
257
+ """
258
+ try:
259
+ # Sanitize content
260
+ sanitized_code = MermaidValidator.sanitize_content(diagram_code)
261
+
262
+ # Validate size
263
+ if len(sanitized_code) > self.max_diagram_size:
264
+ error_msg = f"Error: Diagram exceeds maximum size of {self.max_diagram_size} characters (current: {len(sanitized_code)})"
265
+ logger.error(f"[DataVisualization] {error_msg}")
266
+ return error_msg
267
+
268
+ # Validate syntax if enabled
269
+ if validate:
270
+ is_valid, error_message = MermaidValidator.validate_syntax(
271
+ sanitized_code,
272
+ expected_type=diagram_type
273
+ )
274
+
275
+ if not is_valid:
276
+ if auto_fix:
277
+ # Attempt to fix common issues
278
+ fixed_code = self._attempt_fix(sanitized_code, diagram_type)
279
+ if fixed_code:
280
+ # Re-validate fixed code
281
+ is_valid, error_message = MermaidValidator.validate_syntax(
282
+ fixed_code,
283
+ expected_type=diagram_type
284
+ )
285
+ if is_valid:
286
+ logger.info(f"[DataVisualization] Auto-fixed diagram syntax issue")
287
+ sanitized_code = fixed_code
288
+ else:
289
+ error_msg = f"Error: Invalid diagram syntax - {error_message}\n\nAttempted auto-fix failed. Please check your Mermaid syntax."
290
+ logger.error(f"[DataVisualization] {error_msg}")
291
+ return error_msg
292
+ else:
293
+ error_msg = f"Error: Invalid diagram syntax - {error_message}"
294
+ logger.error(f"[DataVisualization] {error_msg}")
295
+ return error_msg
296
+
297
+ # Log successful validation
298
+ logger.info(
299
+ f"[DataVisualization] Emitting {diagram_type} diagram "
300
+ f"({len(sanitized_code)} chars)"
301
+ )
302
+
303
+ # Format the diagram event
304
+ output = f"""<<DIAGRAM_START>>
305
+ ```mermaid
306
+ {sanitized_code.strip()}
307
+ ```
308
+ <<DIAGRAM_END>>"""
309
+
310
+ # Stream if callback provided
311
+ if self.stream_callback:
312
+ try:
313
+ self.stream_callback(output)
314
+ except Exception as stream_error:
315
+ logger.error(
316
+ f"[DataVisualization] Stream callback failed: {stream_error}"
317
+ )
318
+ # Continue anyway - the output is still returned
319
+
320
+ return output
321
+
322
+ except Exception as e:
323
+ error_msg = f"Error: Failed to emit diagram - {str(e)}"
324
+ logger.error(f"[DataVisualization] {error_msg}", exc_info=True)
325
+ return error_msg
326
+
327
+ def _attempt_fix(self, diagram_code: str, diagram_type: str) -> Optional[str]:
328
+ """
329
+ Attempt to auto-fix common diagram syntax issues.
330
+
331
+ Args:
332
+ diagram_code: The diagram code with issues
333
+ diagram_type: The type of diagram
334
+
335
+ Returns:
336
+ Fixed diagram code or None if can't be fixed
337
+ """
338
+ try:
339
+ lines = diagram_code.split('\n')
340
+ fixed_lines = []
341
+
342
+ for line in lines:
343
+ # Remove trailing semicolons (common mistake)
344
+ if line.rstrip().endswith(';'):
345
+ line = line.rstrip()[:-1]
346
+
347
+ # Fix common arrow syntax issues
348
+ line = line.replace('-->', '-->').replace('--->', '-->')
349
+
350
+ fixed_lines.append(line)
351
+
352
+ return '\n'.join(fixed_lines)
353
+ except Exception as e:
354
+ logger.warning(f"[DataVisualization] Auto-fix failed: {e}")
355
+ return None
356
+
357
+ def create_diagram(
358
+ self, diagram_code: str, diagram_type: str = "flowchart"
359
+ ) -> str:
360
+ """
361
+ Create a diagram from Mermaid syntax.
362
+
363
+ Args:
364
+ diagram_code: Complete Mermaid diagram code
365
+ diagram_type: Type of diagram (for metadata)
366
+
367
+ Returns:
368
+ Success message with diagram event
369
+
370
+ Example:
371
+ create_diagram('''
372
+ flowchart TD
373
+ A[Start] --> B{Decision}
374
+ B -->|Yes| C[Action 1]
375
+ B -->|No| D[Action 2]
376
+ ''', 'flowchart')
377
+ """
378
+ return self._emit_diagram(diagram_code, diagram_type)
379
+
380
+ def create_flowchart(
381
+ self,
382
+ title: str,
383
+ nodes: str,
384
+ direction: str = "TD",
385
+ ) -> str:
386
+ """
387
+ Create a flowchart diagram with automatic validation and error recovery.
388
+
389
+ Args:
390
+ title: Title of the flowchart
391
+ nodes: Mermaid flowchart node definitions
392
+ direction: Direction (TD=top-down, LR=left-right, RL=right-left, BT=bottom-top)
393
+
394
+ Returns:
395
+ Success message with diagram event or error message
396
+
397
+ Example:
398
+ create_flowchart(
399
+ title="User Login Flow",
400
+ nodes='''
401
+ A[User] --> B[Login Page]
402
+ B --> C{Valid?}
403
+ C -->|Yes| D[Dashboard]
404
+ C -->|No| E[Error]
405
+ ''',
406
+ direction="TD"
407
+ )
408
+ """
409
+ try:
410
+ # Validate direction parameter
411
+ valid_directions = ['TD', 'TB', 'BT', 'RL', 'LR']
412
+ direction = direction.upper()
413
+ if direction not in valid_directions:
414
+ logger.warning(
415
+ f"[DataVisualization] Invalid direction '{direction}', "
416
+ f"defaulting to 'TD'. Valid: {valid_directions}"
417
+ )
418
+ direction = 'TD'
419
+
420
+ # Sanitize title
421
+ title = title.replace('"', "'").strip()
422
+ if not title:
423
+ title = "Flowchart"
424
+
425
+ # Build diagram
426
+ diagram = f"""---
427
+ title: {title}
428
+ ---
429
+ flowchart {direction}
430
+ {nodes.strip()}"""
431
+
432
+ return self._emit_diagram(diagram, "flowchart")
433
+
434
+ except Exception as e:
435
+ error_msg = f"Error creating flowchart: {str(e)}"
436
+ logger.error(f"[DataVisualization] {error_msg}", exc_info=True)
437
+ return error_msg
438
+
439
+ def create_sequence_diagram(
440
+ self,
441
+ title: str,
442
+ participants: list[str],
443
+ interactions: str,
444
+ ) -> str:
445
+ """
446
+ Create a sequence diagram.
447
+
448
+ Args:
449
+ title: Title of the sequence diagram
450
+ participants: List of participant names
451
+ interactions: Mermaid sequence diagram interactions
452
+
453
+ Returns:
454
+ Success message with diagram event
455
+
456
+ Example:
457
+ create_sequence_diagram(
458
+ title="API Authentication Flow",
459
+ participants=["Client", "API", "Database"],
460
+ interactions='''
461
+ Client->>API: POST /login
462
+ API->>Database: Verify credentials
463
+ Database-->>API: User data
464
+ API-->>Client: JWT token
465
+ '''
466
+ )
467
+ """
468
+ participant_defs = "\n".join([f" participant {p}" for p in participants])
469
+ diagram = f"""---
470
+ title: {title}
471
+ ---
472
+ sequenceDiagram
473
+ {participant_defs}
474
+ {interactions.strip()}"""
475
+ return self._emit_diagram(diagram, "sequence")
476
+
477
+ def create_class_diagram(
478
+ self,
479
+ title: str,
480
+ classes: str,
481
+ ) -> str:
482
+ """
483
+ Create a class diagram.
484
+
485
+ Args:
486
+ title: Title of the class diagram
487
+ classes: Mermaid class diagram definitions
488
+
489
+ Returns:
490
+ Success message with diagram event
491
+
492
+ Example:
493
+ create_class_diagram(
494
+ title="User Management System",
495
+ classes='''
496
+ class User {
497
+ +String name
498
+ +String email
499
+ +login()
500
+ +logout()
501
+ }
502
+ class Admin {
503
+ +String permissions
504
+ +deleteUser()
505
+ }
506
+ User <|-- Admin
507
+ '''
508
+ )
509
+ """
510
+ diagram = f"""---
511
+ title: {title}
512
+ ---
513
+ classDiagram
514
+ {classes.strip()}"""
515
+ return self._emit_diagram(diagram, "class")
516
+
517
+ def create_er_diagram(
518
+ self,
519
+ title: str,
520
+ entities: str,
521
+ ) -> str:
522
+ """
523
+ Create an Entity-Relationship diagram.
524
+
525
+ Args:
526
+ title: Title of the ER diagram
527
+ entities: Mermaid ER diagram definitions
528
+
529
+ Returns:
530
+ Success message with diagram event
531
+
532
+ Example:
533
+ create_er_diagram(
534
+ title="E-commerce Database Schema",
535
+ entities='''
536
+ CUSTOMER ||--o{ ORDER : places
537
+ ORDER ||--|{ LINE-ITEM : contains
538
+ PRODUCT ||--o{ LINE-ITEM : includes
539
+ '''
540
+ )
541
+ """
542
+ diagram = f"""---
543
+ title: {title}
544
+ ---
545
+ erDiagram
546
+ {entities.strip()}"""
547
+ return self._emit_diagram(diagram, "er")
548
+
549
+ def create_gantt_chart(
550
+ self,
551
+ title: str,
552
+ tasks: str,
553
+ date_format: str = "YYYY-MM-DD",
554
+ ) -> str:
555
+ """
556
+ Create a Gantt chart.
557
+
558
+ Args:
559
+ title: Title of the Gantt chart
560
+ tasks: Mermaid Gantt chart task definitions
561
+ date_format: Date format (default: YYYY-MM-DD)
562
+
563
+ Returns:
564
+ Success message with diagram event
565
+
566
+ Example:
567
+ create_gantt_chart(
568
+ title="Project Timeline",
569
+ tasks='''
570
+ section Planning
571
+ Requirements : 2024-01-01, 2w
572
+ Design : 2024-01-15, 1w
573
+ section Development
574
+ Backend : 2024-01-22, 3w
575
+ Frontend : 2024-02-05, 3w
576
+ '''
577
+ )
578
+ """
579
+ diagram = f"""---
580
+ title: {title}
581
+ ---
582
+ gantt
583
+ dateFormat {date_format}
584
+ {tasks.strip()}"""
585
+ return self._emit_diagram(diagram, "gantt")
586
+
587
+ def create_pie_chart(
588
+ self,
589
+ title: str,
590
+ data: dict[str, float],
591
+ ) -> str:
592
+ """
593
+ Create a pie chart with data validation.
594
+
595
+ Args:
596
+ title: Title of the pie chart
597
+ data: Dictionary of label: value pairs
598
+
599
+ Returns:
600
+ Success message with diagram event or error message
601
+
602
+ Example:
603
+ create_pie_chart(
604
+ title="Revenue by Product",
605
+ data={
606
+ "Product A": 35.5,
607
+ "Product B": 28.3,
608
+ "Product C": 20.1,
609
+ "Product D": 16.1
610
+ }
611
+ )
612
+ """
613
+ try:
614
+ # Validate data
615
+ if not data or not isinstance(data, dict):
616
+ return "Error: Pie chart data must be a non-empty dictionary"
617
+
618
+ if len(data) == 0:
619
+ return "Error: Pie chart must have at least one data point"
620
+
621
+ if len(data) > 50:
622
+ logger.warning(
623
+ f"[DataVisualization] Pie chart has {len(data)} slices, "
624
+ "which may be hard to read. Consider grouping data."
625
+ )
626
+
627
+ # Validate and sanitize data
628
+ validated_data = {}
629
+ for label, value in data.items():
630
+ # Sanitize label
631
+ clean_label = str(label).replace('"', "'").strip()
632
+ if not clean_label:
633
+ clean_label = "Unnamed"
634
+
635
+ # Validate value
636
+ try:
637
+ numeric_value = float(value)
638
+ if numeric_value < 0:
639
+ logger.warning(
640
+ f"[DataVisualization] Negative value for '{clean_label}': {numeric_value}, "
641
+ "using absolute value"
642
+ )
643
+ numeric_value = abs(numeric_value)
644
+ validated_data[clean_label] = numeric_value
645
+ except (ValueError, TypeError):
646
+ logger.warning(
647
+ f"[DataVisualization] Invalid value for '{clean_label}': {value}, skipping"
648
+ )
649
+ continue
650
+
651
+ if not validated_data:
652
+ return "Error: No valid data points after validation"
653
+
654
+ # Sanitize title
655
+ title = title.replace('"', "'").strip()
656
+ if not title:
657
+ title = "Pie Chart"
658
+
659
+ # Build diagram
660
+ data_lines = "\n".join(
661
+ [f' "{label}" : {value}' for label, value in validated_data.items()]
662
+ )
663
+ diagram = f"""---
664
+ title: {title}
665
+ ---
666
+ pie
667
+ {data_lines}"""
668
+
669
+ return self._emit_diagram(diagram, "pie")
670
+
671
+ except Exception as e:
672
+ error_msg = f"Error creating pie chart: {str(e)}"
673
+ logger.error(f"[DataVisualization] {error_msg}", exc_info=True)
674
+ return error_msg
675
+
676
+ def create_state_diagram(
677
+ self,
678
+ title: str,
679
+ states: str,
680
+ ) -> str:
681
+ """
682
+ Create a state diagram.
683
+
684
+ Args:
685
+ title: Title of the state diagram
686
+ states: Mermaid state diagram definitions
687
+
688
+ Returns:
689
+ Success message with diagram event
690
+
691
+ Example:
692
+ create_state_diagram(
693
+ title="Order Processing States",
694
+ states='''
695
+ [*] --> Pending
696
+ Pending --> Processing : Payment received
697
+ Processing --> Shipped : Items packed
698
+ Shipped --> Delivered : Delivery confirmed
699
+ Delivered --> [*]
700
+ Processing --> Cancelled : Cancel request
701
+ Cancelled --> [*]
702
+ '''
703
+ )
704
+ """
705
+ diagram = f"""---
706
+ title: {title}
707
+ ---
708
+ stateDiagram-v2
709
+ {states.strip()}"""
710
+ return self._emit_diagram(diagram, "state")
711
+
712
+ def create_git_graph(
713
+ self,
714
+ title: str,
715
+ commits: str,
716
+ ) -> str:
717
+ """
718
+ Create a Git graph diagram.
719
+
720
+ Args:
721
+ title: Title of the Git graph
722
+ commits: Mermaid Git graph commit definitions
723
+
724
+ Returns:
725
+ Success message with diagram event
726
+
727
+ Example:
728
+ create_git_graph(
729
+ title="Feature Branch Workflow",
730
+ commits='''
731
+ commit
732
+ branch develop
733
+ checkout develop
734
+ commit
735
+ branch feature
736
+ checkout feature
737
+ commit
738
+ commit
739
+ checkout develop
740
+ merge feature
741
+ checkout main
742
+ merge develop
743
+ '''
744
+ )
745
+ """
746
+ diagram = f"""---
747
+ title: {title}
748
+ ---
749
+ gitGraph
750
+ {commits.strip()}"""
751
+ return self._emit_diagram(diagram, "git")
752
+
753
+ def create_user_journey(
754
+ self,
755
+ title: str,
756
+ journey: str,
757
+ ) -> str:
758
+ """
759
+ Create a user journey diagram.
760
+
761
+ Args:
762
+ title: Title of the user journey
763
+ journey: Mermaid user journey definitions
764
+
765
+ Returns:
766
+ Success message with diagram event
767
+
768
+ Example:
769
+ create_user_journey(
770
+ title="Customer Onboarding Journey",
771
+ journey='''
772
+ section Sign up
773
+ Visit website: 5: User
774
+ Create account: 3: User
775
+ section Getting Started
776
+ Complete profile: 4: User
777
+ First purchase: 5: User
778
+ '''
779
+ )
780
+ """
781
+ diagram = f"""---
782
+ title: {title}
783
+ ---
784
+ journey
785
+ {journey.strip()}"""
786
+ return self._emit_diagram(diagram, "journey")
787
+
788
+ def create_quadrant_chart(
789
+ self,
790
+ title: str,
791
+ x_axis: str,
792
+ y_axis: str,
793
+ items: str,
794
+ ) -> str:
795
+ """
796
+ Create a quadrant chart.
797
+
798
+ Args:
799
+ title: Title of the quadrant chart
800
+ x_axis: X-axis label (left --> right)
801
+ y_axis: Y-axis label (bottom --> top)
802
+ items: Mermaid quadrant chart item definitions
803
+
804
+ Returns:
805
+ Success message with diagram event
806
+
807
+ Example:
808
+ create_quadrant_chart(
809
+ title="Product Priority Matrix",
810
+ x_axis="Effort",
811
+ y_axis="Impact",
812
+ items='''
813
+ Feature A: [0.8, 0.9]
814
+ Feature B: [0.3, 0.7]
815
+ Feature C: [0.6, 0.4]
816
+ Feature D: [0.2, 0.2]
817
+ '''
818
+ )
819
+ """
820
+ diagram = f"""---
821
+ title: {title}
822
+ ---
823
+ quadrantChart
824
+ x-axis {x_axis}
825
+ y-axis {y_axis}
826
+ {items.strip()}"""
827
+ return self._emit_diagram(diagram, "quadrant")