mito-ai 0.1.50__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 (205) hide show
  1. mito_ai/__init__.py +114 -0
  2. mito_ai/_version.py +4 -0
  3. mito_ai/anthropic_client.py +334 -0
  4. mito_ai/app_deploy/__init__.py +6 -0
  5. mito_ai/app_deploy/app_deploy_utils.py +44 -0
  6. mito_ai/app_deploy/handlers.py +345 -0
  7. mito_ai/app_deploy/models.py +98 -0
  8. mito_ai/app_manager/__init__.py +4 -0
  9. mito_ai/app_manager/handlers.py +167 -0
  10. mito_ai/app_manager/models.py +71 -0
  11. mito_ai/app_manager/utils.py +24 -0
  12. mito_ai/auth/README.md +18 -0
  13. mito_ai/auth/__init__.py +6 -0
  14. mito_ai/auth/handlers.py +96 -0
  15. mito_ai/auth/urls.py +13 -0
  16. mito_ai/chat_history/handlers.py +63 -0
  17. mito_ai/chat_history/urls.py +32 -0
  18. mito_ai/completions/completion_handlers/__init__.py +3 -0
  19. mito_ai/completions/completion_handlers/agent_auto_error_fixup_handler.py +59 -0
  20. mito_ai/completions/completion_handlers/agent_execution_handler.py +66 -0
  21. mito_ai/completions/completion_handlers/chat_completion_handler.py +141 -0
  22. mito_ai/completions/completion_handlers/code_explain_handler.py +113 -0
  23. mito_ai/completions/completion_handlers/completion_handler.py +42 -0
  24. mito_ai/completions/completion_handlers/inline_completer_handler.py +48 -0
  25. mito_ai/completions/completion_handlers/smart_debug_handler.py +160 -0
  26. mito_ai/completions/completion_handlers/utils.py +147 -0
  27. mito_ai/completions/handlers.py +415 -0
  28. mito_ai/completions/message_history.py +401 -0
  29. mito_ai/completions/models.py +404 -0
  30. mito_ai/completions/prompt_builders/__init__.py +3 -0
  31. mito_ai/completions/prompt_builders/agent_execution_prompt.py +57 -0
  32. mito_ai/completions/prompt_builders/agent_smart_debug_prompt.py +160 -0
  33. mito_ai/completions/prompt_builders/agent_system_message.py +472 -0
  34. mito_ai/completions/prompt_builders/chat_name_prompt.py +15 -0
  35. mito_ai/completions/prompt_builders/chat_prompt.py +116 -0
  36. mito_ai/completions/prompt_builders/chat_system_message.py +92 -0
  37. mito_ai/completions/prompt_builders/explain_code_prompt.py +32 -0
  38. mito_ai/completions/prompt_builders/inline_completer_prompt.py +197 -0
  39. mito_ai/completions/prompt_builders/prompt_constants.py +170 -0
  40. mito_ai/completions/prompt_builders/smart_debug_prompt.py +199 -0
  41. mito_ai/completions/prompt_builders/utils.py +84 -0
  42. mito_ai/completions/providers.py +284 -0
  43. mito_ai/constants.py +63 -0
  44. mito_ai/db/__init__.py +3 -0
  45. mito_ai/db/crawlers/__init__.py +6 -0
  46. mito_ai/db/crawlers/base_crawler.py +61 -0
  47. mito_ai/db/crawlers/constants.py +43 -0
  48. mito_ai/db/crawlers/snowflake.py +71 -0
  49. mito_ai/db/handlers.py +168 -0
  50. mito_ai/db/models.py +31 -0
  51. mito_ai/db/urls.py +34 -0
  52. mito_ai/db/utils.py +185 -0
  53. mito_ai/docker/mssql/compose.yml +37 -0
  54. mito_ai/docker/mssql/init/setup.sql +21 -0
  55. mito_ai/docker/mysql/compose.yml +18 -0
  56. mito_ai/docker/mysql/init/setup.sql +13 -0
  57. mito_ai/docker/oracle/compose.yml +17 -0
  58. mito_ai/docker/oracle/init/setup.sql +20 -0
  59. mito_ai/docker/postgres/compose.yml +17 -0
  60. mito_ai/docker/postgres/init/setup.sql +13 -0
  61. mito_ai/enterprise/__init__.py +3 -0
  62. mito_ai/enterprise/utils.py +15 -0
  63. mito_ai/file_uploads/__init__.py +3 -0
  64. mito_ai/file_uploads/handlers.py +248 -0
  65. mito_ai/file_uploads/urls.py +21 -0
  66. mito_ai/gemini_client.py +232 -0
  67. mito_ai/log/handlers.py +38 -0
  68. mito_ai/log/urls.py +21 -0
  69. mito_ai/logger.py +37 -0
  70. mito_ai/openai_client.py +382 -0
  71. mito_ai/path_utils.py +70 -0
  72. mito_ai/rules/handlers.py +44 -0
  73. mito_ai/rules/urls.py +22 -0
  74. mito_ai/rules/utils.py +56 -0
  75. mito_ai/settings/handlers.py +41 -0
  76. mito_ai/settings/urls.py +20 -0
  77. mito_ai/settings/utils.py +42 -0
  78. mito_ai/streamlit_conversion/agent_utils.py +37 -0
  79. mito_ai/streamlit_conversion/prompts/prompt_constants.py +172 -0
  80. mito_ai/streamlit_conversion/prompts/prompt_utils.py +10 -0
  81. mito_ai/streamlit_conversion/prompts/streamlit_app_creation_prompt.py +46 -0
  82. mito_ai/streamlit_conversion/prompts/streamlit_error_correction_prompt.py +28 -0
  83. mito_ai/streamlit_conversion/prompts/streamlit_finish_todo_prompt.py +45 -0
  84. mito_ai/streamlit_conversion/prompts/streamlit_system_prompt.py +56 -0
  85. mito_ai/streamlit_conversion/prompts/update_existing_app_prompt.py +50 -0
  86. mito_ai/streamlit_conversion/search_replace_utils.py +94 -0
  87. mito_ai/streamlit_conversion/streamlit_agent_handler.py +144 -0
  88. mito_ai/streamlit_conversion/streamlit_utils.py +85 -0
  89. mito_ai/streamlit_conversion/validate_streamlit_app.py +105 -0
  90. mito_ai/streamlit_preview/__init__.py +6 -0
  91. mito_ai/streamlit_preview/handlers.py +111 -0
  92. mito_ai/streamlit_preview/manager.py +152 -0
  93. mito_ai/streamlit_preview/urls.py +22 -0
  94. mito_ai/streamlit_preview/utils.py +29 -0
  95. mito_ai/tests/__init__.py +3 -0
  96. mito_ai/tests/chat_history/test_chat_history.py +211 -0
  97. mito_ai/tests/completions/completion_handlers_utils_test.py +190 -0
  98. mito_ai/tests/conftest.py +53 -0
  99. mito_ai/tests/create_agent_system_message_prompt_test.py +22 -0
  100. mito_ai/tests/data/prompt_lg.py +69 -0
  101. mito_ai/tests/data/prompt_sm.py +6 -0
  102. mito_ai/tests/data/prompt_xl.py +13 -0
  103. mito_ai/tests/data/stock_data.sqlite3 +0 -0
  104. mito_ai/tests/db/conftest.py +39 -0
  105. mito_ai/tests/db/connections_test.py +102 -0
  106. mito_ai/tests/db/mssql_test.py +29 -0
  107. mito_ai/tests/db/mysql_test.py +29 -0
  108. mito_ai/tests/db/oracle_test.py +29 -0
  109. mito_ai/tests/db/postgres_test.py +29 -0
  110. mito_ai/tests/db/schema_test.py +93 -0
  111. mito_ai/tests/db/sqlite_test.py +31 -0
  112. mito_ai/tests/db/test_db_constants.py +61 -0
  113. mito_ai/tests/deploy_app/test_app_deploy_utils.py +89 -0
  114. mito_ai/tests/file_uploads/__init__.py +2 -0
  115. mito_ai/tests/file_uploads/test_handlers.py +282 -0
  116. mito_ai/tests/message_history/test_generate_short_chat_name.py +120 -0
  117. mito_ai/tests/message_history/test_message_history_utils.py +469 -0
  118. mito_ai/tests/open_ai_utils_test.py +152 -0
  119. mito_ai/tests/performance_test.py +329 -0
  120. mito_ai/tests/providers/test_anthropic_client.py +447 -0
  121. mito_ai/tests/providers/test_azure.py +631 -0
  122. mito_ai/tests/providers/test_capabilities.py +120 -0
  123. mito_ai/tests/providers/test_gemini_client.py +195 -0
  124. mito_ai/tests/providers/test_mito_server_utils.py +448 -0
  125. mito_ai/tests/providers/test_model_resolution.py +130 -0
  126. mito_ai/tests/providers/test_openai_client.py +57 -0
  127. mito_ai/tests/providers/test_provider_completion_exception.py +66 -0
  128. mito_ai/tests/providers/test_provider_limits.py +42 -0
  129. mito_ai/tests/providers/test_providers.py +382 -0
  130. mito_ai/tests/providers/test_retry_logic.py +389 -0
  131. mito_ai/tests/providers/test_stream_mito_server_utils.py +140 -0
  132. mito_ai/tests/providers/utils.py +85 -0
  133. mito_ai/tests/rules/conftest.py +26 -0
  134. mito_ai/tests/rules/rules_test.py +117 -0
  135. mito_ai/tests/server_limits_test.py +406 -0
  136. mito_ai/tests/settings/conftest.py +26 -0
  137. mito_ai/tests/settings/settings_test.py +70 -0
  138. mito_ai/tests/settings/test_settings_constants.py +9 -0
  139. mito_ai/tests/streamlit_conversion/__init__.py +3 -0
  140. mito_ai/tests/streamlit_conversion/test_apply_search_replace.py +240 -0
  141. mito_ai/tests/streamlit_conversion/test_streamlit_agent_handler.py +246 -0
  142. mito_ai/tests/streamlit_conversion/test_streamlit_utils.py +193 -0
  143. mito_ai/tests/streamlit_conversion/test_validate_streamlit_app.py +112 -0
  144. mito_ai/tests/streamlit_preview/test_streamlit_preview_handler.py +118 -0
  145. mito_ai/tests/streamlit_preview/test_streamlit_preview_manager.py +292 -0
  146. mito_ai/tests/test_constants.py +47 -0
  147. mito_ai/tests/test_telemetry.py +12 -0
  148. mito_ai/tests/user/__init__.py +2 -0
  149. mito_ai/tests/user/test_user.py +120 -0
  150. mito_ai/tests/utils/__init__.py +3 -0
  151. mito_ai/tests/utils/test_anthropic_utils.py +162 -0
  152. mito_ai/tests/utils/test_gemini_utils.py +98 -0
  153. mito_ai/tests/version_check_test.py +169 -0
  154. mito_ai/user/handlers.py +45 -0
  155. mito_ai/user/urls.py +21 -0
  156. mito_ai/utils/__init__.py +3 -0
  157. mito_ai/utils/anthropic_utils.py +168 -0
  158. mito_ai/utils/create.py +94 -0
  159. mito_ai/utils/db.py +74 -0
  160. mito_ai/utils/error_classes.py +42 -0
  161. mito_ai/utils/gemini_utils.py +133 -0
  162. mito_ai/utils/message_history_utils.py +87 -0
  163. mito_ai/utils/mito_server_utils.py +242 -0
  164. mito_ai/utils/open_ai_utils.py +200 -0
  165. mito_ai/utils/provider_utils.py +49 -0
  166. mito_ai/utils/schema.py +86 -0
  167. mito_ai/utils/server_limits.py +152 -0
  168. mito_ai/utils/telemetry_utils.py +480 -0
  169. mito_ai/utils/utils.py +89 -0
  170. mito_ai/utils/version_utils.py +94 -0
  171. mito_ai/utils/websocket_base.py +88 -0
  172. mito_ai/version_check.py +60 -0
  173. mito_ai-0.1.50.data/data/etc/jupyter/jupyter_server_config.d/mito_ai.json +7 -0
  174. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/build_log.json +728 -0
  175. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/package.json +243 -0
  176. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/package.json.orig +238 -0
  177. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/toolbar-buttons.json +37 -0
  178. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.8f1845da6bf2b128c049.js +21602 -0
  179. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.8f1845da6bf2b128c049.js.map +1 -0
  180. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/node_modules_process_browser_js.4b128e94d31a81ebd209.js +198 -0
  181. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/node_modules_process_browser_js.4b128e94d31a81ebd209.js.map +1 -0
  182. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.78d3ccb73e7ca1da3aae.js +619 -0
  183. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.78d3ccb73e7ca1da3aae.js.map +1 -0
  184. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/style.js +4 -0
  185. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/style_index_js.5876024bb17dbd6a3ee6.js +712 -0
  186. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/style_index_js.5876024bb17dbd6a3ee6.js.map +1 -0
  187. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_apis_signOut_mjs-node_module-75790d.688c25857e7b81b1740f.js +533 -0
  188. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_apis_signOut_mjs-node_module-75790d.688c25857e7b81b1740f.js.map +1 -0
  189. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_tokenProvider_tokenProvider_-72f1c8.a917210f057fcfe224ad.js +6941 -0
  190. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_tokenProvider_tokenProvider_-72f1c8.a917210f057fcfe224ad.js.map +1 -0
  191. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_dist_esm_index_mjs.6bac1a8c4cc93f15f6b7.js +1021 -0
  192. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_dist_esm_index_mjs.6bac1a8c4cc93f15f6b7.js.map +1 -0
  193. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_ui-react_dist_esm_index_mjs.4fcecd65bef9e9847609.js +59698 -0
  194. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_ui-react_dist_esm_index_mjs.4fcecd65bef9e9847609.js.map +1 -0
  195. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_react-dom_client_js-node_modules_aws-amplify_ui-react_dist_styles_css.b43d4249e4d3dac9ad7b.js +7440 -0
  196. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_react-dom_client_js-node_modules_aws-amplify_ui-react_dist_styles_css.b43d4249e4d3dac9ad7b.js.map +1 -0
  197. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.3f6754ac5116d47de76b.js +2792 -0
  198. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.3f6754ac5116d47de76b.js.map +1 -0
  199. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_vscode-diff_dist_index_js.ea55f1f9346638aafbcf.js +4859 -0
  200. mito_ai-0.1.50.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_vscode-diff_dist_index_js.ea55f1f9346638aafbcf.js.map +1 -0
  201. mito_ai-0.1.50.dist-info/METADATA +221 -0
  202. mito_ai-0.1.50.dist-info/RECORD +205 -0
  203. mito_ai-0.1.50.dist-info/WHEEL +4 -0
  204. mito_ai-0.1.50.dist-info/entry_points.txt +2 -0
  205. mito_ai-0.1.50.dist-info/licenses/LICENSE +3 -0
@@ -0,0 +1,152 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ from typing import Final
5
+ from mito_ai.utils.db import (
6
+ get_chat_completion_count,
7
+ get_autocomplete_count,
8
+ get_first_completion_date,
9
+ get_last_reset_date,
10
+ get_user_field,
11
+ set_user_field
12
+ )
13
+ from mito_ai.utils.schema import (
14
+ UJ_MITO_AI_FIRST_USAGE_DATE,
15
+ UJ_AI_MITO_API_NUM_USAGES,
16
+ UJ_MITO_AI_LAST_RESET_DATE,
17
+ UJ_AI_MITO_AUTOCOMPLETE_NUM_USAGES
18
+ )
19
+ from mito_ai.utils.telemetry_utils import (MITO_SERVER_FREE_TIER_LIMIT_REACHED, log)
20
+ from mito_ai.utils.version_utils import is_pro
21
+ from mito_ai.completions.models import MessageType
22
+ from datetime import datetime, timedelta
23
+
24
+ """
25
+ IT IS EXPRESSLY AGAINST THE SOFTWARE LICENSE TO MODIFY, BYPASS, OR OTHERWISE
26
+ CIRCUMVENT THE QUOTA LIMITS OF THE FREE TIER. We want to provide users with a
27
+ free tier, but running AI models is expensive, so we need to limit the usage
28
+ or we will no longer be able to provide this free tier.
29
+ """
30
+ # Monthly chat completions limit for free tier users
31
+ OS_MONTHLY_AI_COMPLETIONS_LIMIT: Final[int] = 150
32
+
33
+ # Monthly autocomplete limit for free tier users
34
+ OS_MONTHLY_AUTOCOMPLETE_LIMIT: Final[int] = 5000
35
+
36
+ def check_mito_server_quota(message_type: MessageType) -> None:
37
+ """
38
+ Checks if the user has exceeded their Mito server quota. Pro users have no limits.
39
+ Raises PermissionError if the user has exceeded their quota.
40
+
41
+ IT IS EXPRESSLY AGAINST THE SOFTWARE LICENSE TO MODIFY, BYPASS, OR OTHERWISE
42
+ CIRCUMVENT THE QUOTA LIMITS OF THE FREE TIER. We want to provide users with a
43
+ free tier, but running AI models is expensive, so we need to limit the usage
44
+ or we will no longer be able to provide this free tier.
45
+ """
46
+ if is_pro():
47
+ return
48
+
49
+ # Get current date
50
+ current_date = datetime.now()
51
+
52
+ # Get the date when the last reset occurred
53
+ last_reset_date_str = get_last_reset_date()
54
+
55
+ # If no last reset date is found, set it to today and reset both counters
56
+ if last_reset_date_str is None:
57
+ set_user_field(UJ_MITO_AI_LAST_RESET_DATE, current_date.strftime("%Y-%m-%d"))
58
+ set_user_field(UJ_AI_MITO_API_NUM_USAGES, 0)
59
+ set_user_field(UJ_AI_MITO_AUTOCOMPLETE_NUM_USAGES, 0)
60
+ return
61
+
62
+ # Convert the last reset date string to a datetime object
63
+ last_reset_date = datetime.strptime(last_reset_date_str, "%Y-%m-%d")
64
+
65
+ # Calculate the date one month from the last reset
66
+ one_month_later = last_reset_date + timedelta(days=30) # Approximating a month as 30 days
67
+
68
+ # If it's been more than a month since the last reset, reset both counters
69
+ if current_date >= one_month_later:
70
+ set_user_field(UJ_AI_MITO_API_NUM_USAGES, 0)
71
+ set_user_field(UJ_AI_MITO_AUTOCOMPLETE_NUM_USAGES, 0)
72
+ set_user_field(UJ_MITO_AI_LAST_RESET_DATE, current_date.strftime("%Y-%m-%d"))
73
+
74
+ # Check the appropriate limit based on message type
75
+ if message_type == MessageType.INLINE_COMPLETION:
76
+ # Check autocomplete limit
77
+ autocomplete_count = get_autocomplete_count()
78
+
79
+ if autocomplete_count >= OS_MONTHLY_AUTOCOMPLETE_LIMIT:
80
+ log(MITO_SERVER_FREE_TIER_LIMIT_REACHED)
81
+ raise PermissionError(MITO_SERVER_FREE_TIER_LIMIT_REACHED)
82
+ else:
83
+ # Check chat completion limit
84
+ completion_count = get_chat_completion_count()
85
+
86
+ if completion_count >= OS_MONTHLY_AI_COMPLETIONS_LIMIT:
87
+ log(MITO_SERVER_FREE_TIER_LIMIT_REACHED)
88
+ raise PermissionError(MITO_SERVER_FREE_TIER_LIMIT_REACHED)
89
+
90
+
91
+ def update_mito_server_quota(message_type: MessageType) -> None:
92
+ """
93
+ Update the user's quota for the Mito Server.
94
+
95
+ IT IS EXPRESSLY AGAINST THE SOFTWARE LICENSE TO MODIFY, BYPASS, OR OTHERWISE
96
+ CIRCUMVENT THE QUOTA LIMITS OF THE FREE TIER. We want to provide users with a
97
+ free tier, but running AI models is expensive, so we need to limit the usage
98
+ or we will no longer be able to provide this free tier.
99
+ """
100
+
101
+ if message_type == MessageType.CHAT_NAME_GENERATION:
102
+ # We do not count the Chat Name Generation message type towards the quota
103
+ return
104
+
105
+ current_date = datetime.now()
106
+ first_usage_date = get_first_completion_date()
107
+ last_reset_date_str = get_last_reset_date()
108
+
109
+ if first_usage_date is None:
110
+ first_usage_date = current_date.strftime("%Y-%m-%d")
111
+ set_user_field(UJ_MITO_AI_FIRST_USAGE_DATE, first_usage_date)
112
+
113
+ # Initialize the reset date if it doesn't exist
114
+ if last_reset_date_str is None:
115
+ last_reset_date_str = current_date.strftime("%Y-%m-%d")
116
+ set_user_field(UJ_MITO_AI_LAST_RESET_DATE, last_reset_date_str)
117
+
118
+ # Convert the last reset date string to a datetime object
119
+ last_reset_date = datetime.strptime(last_reset_date_str, "%Y-%m-%d")
120
+
121
+ # Calculate the date one month from the last reset
122
+ one_month_later = last_reset_date + timedelta(days=30)
123
+
124
+ # If it's been more than a month since the last reset, reset both counters
125
+ # and make today's date the new reset date
126
+ if current_date >= one_month_later:
127
+ set_user_field(UJ_AI_MITO_API_NUM_USAGES, 0)
128
+ set_user_field(UJ_AI_MITO_AUTOCOMPLETE_NUM_USAGES, 0)
129
+ last_reset_date_str = current_date.strftime("%Y-%m-%d")
130
+ set_user_field(UJ_MITO_AI_LAST_RESET_DATE, last_reset_date_str)
131
+
132
+ # Update the appropriate usage counter based on message type
133
+ if message_type == MessageType.INLINE_COMPLETION:
134
+ # Increment autocomplete count
135
+ autocomplete_count = get_autocomplete_count()
136
+ autocomplete_count = 1 if autocomplete_count is None else autocomplete_count + 1
137
+
138
+ try:
139
+ set_user_field(UJ_AI_MITO_AUTOCOMPLETE_NUM_USAGES, autocomplete_count)
140
+ except Exception as e:
141
+ raise e
142
+ else:
143
+ # Increment chat completion count
144
+ completion_count = get_chat_completion_count()
145
+ completion_count = 1 if completion_count is None else completion_count + 1
146
+
147
+ try:
148
+ set_user_field(UJ_AI_MITO_API_NUM_USAGES, completion_count)
149
+ set_user_field(UJ_MITO_AI_FIRST_USAGE_DATE, first_usage_date)
150
+ except Exception as e:
151
+ raise e
152
+
@@ -0,0 +1,480 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ import json
5
+ import os
6
+ from typing import Any, Dict, Literal, Optional, List
7
+ from mito_ai.utils.version_utils import MITOSHEET_HELPER_PRIVATE, is_pro
8
+ from mito_ai.utils.schema import UJ_AI_MITO_API_NUM_USAGES, UJ_MITOSHEET_TELEMETRY, UJ_STATIC_USER_ID, UJ_USER_EMAIL, UJ_FEEDBACKS_V2
9
+ from mito_ai.utils.db import get_user_field
10
+ from mito_ai._version import __version__
11
+ from mito_ai.utils.utils import is_running_test
12
+ from mito_ai.completions.models import MessageType
13
+ import analytics
14
+
15
+ WRITE_KEY = '6I7ptc5wcIGC4WZ0N1t0NXvvAbjRGUgX'
16
+ analytics.write_key = WRITE_KEY
17
+
18
+ # If you want, you can optionally choose to print logs
19
+ # helpful for debugging.
20
+ PRINT_LOGS = False
21
+
22
+ #################################
23
+ # Mito AI Completion
24
+ # Constants for logging the success or error of Mito AI
25
+ MITO_AI_COMPLETION_SUCCESS = 'mito_ai_success'
26
+ MITO_AI_COMPLETION_ERROR = 'mito_ai_error'
27
+ MITO_AI_COMPLETION_RETRY = 'mito_ai_retry'
28
+
29
+ # Params
30
+ # - logging the type of key
31
+ KEY_TYPE_PARAM = 'AI_key_type'
32
+ MITO_SERVER_KEY: Literal['mito_server_key'] = 'mito_server_key'
33
+ USER_KEY: Literal['user_key'] = 'user_key'
34
+ # - logging if the user is in dev mode
35
+ IS_DEV_MODE_PARAM = 'is_dev_mode'
36
+
37
+ # - logging the number of usages of the Mito server
38
+ MITO_SERVER_NUM_USAGES = 'mito_server_num_usages'
39
+ #################################
40
+
41
+ #################################
42
+ # Mito Server Free Tier Reached
43
+ MITO_SERVER_FREE_TIER_LIMIT_REACHED = 'mito_server_free_tier_limit_reached'
44
+ #################################
45
+
46
+ def is_dev_mode() -> bool:
47
+ """
48
+ Check if mito-ai is installed in editable/development mode.
49
+
50
+ This function detects editable installs using the modern PEP 660 standard
51
+ (pip >= 21.3). Works for most development scenarios where developers use
52
+ `pip install -e .`
53
+
54
+ Returns:
55
+ bool: True if running in development mode, False otherwise.
56
+
57
+ Limitations:
58
+ - Requires pip >= 21.3 for reliable detection
59
+ - Won't detect manual PYTHONPATH manipulation
60
+ - Won't detect legacy .egg-link installations (very old pip)
61
+
62
+ Note: This is a best-effort detection. For 100% reliability, consider
63
+ also setting a MITO_DEVELOPER_MODE environment variable.
64
+ """
65
+ try:
66
+ import importlib.metadata
67
+ import json
68
+
69
+ dist = importlib.metadata.distribution('mito-ai')
70
+ direct_url_text = dist.read_text('direct_url.json')
71
+ if direct_url_text:
72
+ direct_url = json.loads(direct_url_text)
73
+ direct_url_dir_info = direct_url.get('dir_info', {})
74
+ editable = direct_url_dir_info.get('editable', None)
75
+ return False if editable is None else editable
76
+ except Exception:
77
+ pass
78
+
79
+ return False
80
+
81
+ def telemetry_turned_on(key_type: Optional[str] = None) -> bool:
82
+ """
83
+ Helper function that tells you if logging is turned on or
84
+ turned off on the entire Mito instance
85
+ """
86
+ # If the user is on the Mito server, then they are sending
87
+ # us their information already
88
+ if key_type == 'mito_server_key':
89
+ return True
90
+
91
+ # If private helper is installed, then we don't log anything
92
+ if MITOSHEET_HELPER_PRIVATE:
93
+ return False
94
+
95
+ # TODO: Check if the an enterprise user has turned telemetry to true
96
+
97
+ # If Mito Pro is on, then don't log anything
98
+ if is_pro():
99
+ return False
100
+
101
+ telemetry = get_user_field(UJ_MITOSHEET_TELEMETRY)
102
+ if telemetry is None:
103
+ return False
104
+
105
+ return bool(telemetry)
106
+
107
+ def identify(key_type: Optional[str] = None, is_electron: Optional[bool] = None) -> None:
108
+ """
109
+ Helper function for identifying a user. We just take
110
+ their python version, mito version, and email.
111
+ """
112
+ if not telemetry_turned_on(key_type):
113
+ return
114
+
115
+ static_user_id = get_user_field(UJ_STATIC_USER_ID)
116
+ user_email = get_user_field(UJ_USER_EMAIL)
117
+ feedbacks_v2 = get_user_field(UJ_FEEDBACKS_V2)
118
+
119
+ params = {
120
+ 'version_mitoai': __version__,
121
+ 'email': user_email,
122
+ 'is_pro': is_pro(),
123
+ 'is_jupyterhub': 'True' if 'JUPYTERHUB_API_URL' in os.environ else 'False',
124
+ 'is_mito_jupyterhub': 'True' if os.getenv('MITO_JUPYTERHUB') is not None else 'False',
125
+ IS_DEV_MODE_PARAM: is_dev_mode(),
126
+ UJ_FEEDBACKS_V2: feedbacks_v2
127
+ }
128
+
129
+ if is_electron is not None:
130
+ # Only update field when we get info from the client. Don't default to False.
131
+ # becase we are only sending this info to the first completion_handler identify call.
132
+ params['is_mito_desktop'] = is_electron
133
+
134
+ if not is_running_test():
135
+ # TODO: If the user is in JupyterLite, we need to do some extra work.
136
+ # You can see this in the mitosheet package.
137
+ try:
138
+ analytics.identify(static_user_id, params)
139
+ except Exception as e:
140
+ pass
141
+
142
+ def chunk_param(param: str, param_name: str, chunk_size: int=250) -> Dict[str, str]:
143
+ """
144
+ Split a string into chunks of 250 characters.
145
+
146
+ Args:
147
+ param: The string to be chunked
148
+ param_name: The name of the param to be chunked (used as prefix for the chunked keys)
149
+ chunk_size: The number of characters in each chunk
150
+
151
+ Returns:
152
+ dict: A dictionary with keys 'response_part_1', 'response_part_2', etc.
153
+ """
154
+
155
+ chunks = {}
156
+
157
+ if not param:
158
+ return {}
159
+
160
+ num_chunks = (len(param) + chunk_size - 1) // chunk_size
161
+
162
+ for i in range(num_chunks):
163
+ start = i * chunk_size
164
+ end = min(start + chunk_size, len(param))
165
+ chunks[f'{param_name}_part_{i + 1}'] = param[start:end]
166
+
167
+ return chunks
168
+
169
+ def log(
170
+ log_event: str,
171
+ params: Optional[Dict[str, Any]]=None,
172
+ error: Optional[BaseException]=None,
173
+ key_type: Optional[Literal['mito_server_key', 'user_key']] = None,
174
+ thread_id: Optional[str] = None
175
+ ) -> None:
176
+ """
177
+ This function is the entry point for all logging.
178
+
179
+ If telemetry is not turned off and we are not running tests,
180
+ we log the ai event
181
+ """
182
+ final_params: Dict[str, Any] = params or {}
183
+
184
+ # Then, make sure to add the user email
185
+ final_params['email'] = get_user_field(UJ_USER_EMAIL)
186
+
187
+ # Add the error if it exists
188
+ if error is not None:
189
+ final_params['error'] = str(error)
190
+
191
+ if thread_id is not None:
192
+ final_params['thread_id'] = thread_id
193
+
194
+ # Process parameters that need chunking
195
+ params_to_remove = []
196
+ params_to_add = {}
197
+
198
+ for param_name, param_value in final_params.items():
199
+ if isinstance(param_value, str) and len(param_value) > 250:
200
+ # Mark for removal
201
+ params_to_remove.append(param_name)
202
+ # Get chunked parameters
203
+ chunked_params = chunk_param(param_value, param_name)
204
+ params_to_add.update(chunked_params)
205
+
206
+ # Apply the changes
207
+ for param_name in params_to_remove:
208
+ del final_params[param_name]
209
+ final_params.update(params_to_add)
210
+
211
+ # Finally, do the acutal logging. We do not log anything when tests are
212
+ # running, or if telemetry is turned off
213
+ if not is_running_test() and telemetry_turned_on(key_type):
214
+ # TODO: If the user is in JupyterLite, we need to do some extra work.
215
+ # You can see this in the mitosheet package.
216
+
217
+ try:
218
+ analytics.track(
219
+ get_user_field(UJ_STATIC_USER_ID),
220
+ log_event,
221
+ final_params
222
+ )
223
+ except Exception as e:
224
+ pass
225
+
226
+
227
+ # If we want to print the logs for debugging reasons, then we print them as well
228
+ if PRINT_LOGS:
229
+ print(
230
+ log_event,
231
+ final_params
232
+ )
233
+
234
+ # TODO: Eventually we want to hook this up to the mito log uploader
235
+ # so enterprises can log usage if they want to.
236
+
237
+ def log_ai_completion_success(
238
+ key_type: Literal['mito_server_key', 'user_key'],
239
+ message_type: MessageType,
240
+ last_message_content: str,
241
+ user_input: str,
242
+ response: Dict[str, Any],
243
+ thread_id: str,
244
+ model: str
245
+ ) -> None:
246
+ """
247
+ Logs AI completion success based on the input location.
248
+
249
+ Args:
250
+ key_type: The type of key that was used to get the AI completion
251
+ message_type: The type of message that was sent to the AI
252
+ last_message_content: The last message sent to the AI
253
+ user_input: The user input that was sent to the AI
254
+ response: The response received from the AI
255
+ thread_id: The thread ID for the conversation
256
+ model: The model that was used to get the AI completion
257
+ """
258
+
259
+ # Params that every log has
260
+ base_params = {
261
+ KEY_TYPE_PARAM: str(key_type),
262
+ IS_DEV_MODE_PARAM: is_dev_mode(),
263
+ 'model': model,
264
+ }
265
+
266
+ try:
267
+ code_cell_input = json.dumps(
268
+ last_message_content.split("Code in the active code cell:")[-1]
269
+ .strip()
270
+ .split("```python")[1]
271
+ .strip()
272
+ .split("```")[0]
273
+ )
274
+
275
+ num_usages = get_user_field(UJ_AI_MITO_API_NUM_USAGES)
276
+ except:
277
+ # Most user prompts will have an associated code cell that serves as the input context.
278
+ # However, types like agent:planning (RIP) do not have a code cell input.
279
+ code_cell_input = ""
280
+ num_usages = -1
281
+
282
+ # Chunk certain params to work around mixpanel's 255 character limit
283
+ code_cell_input_chunks = chunk_param(code_cell_input, "code_cell_input")
284
+ response_chunks = chunk_param(response["completion"], "response")
285
+
286
+ for chunk_key, chunk_value in code_cell_input_chunks.items():
287
+ base_params[chunk_key] = chunk_value
288
+
289
+ for chunk_key, chunk_value in response_chunks.items():
290
+ base_params[chunk_key] = chunk_value
291
+
292
+ # Log number of usages (for mito server)
293
+ if num_usages is not None:
294
+ base_params[MITO_SERVER_NUM_USAGES] = str(num_usages)
295
+
296
+ if message_type == MessageType.SMART_DEBUG:
297
+ error_message = (
298
+ last_message_content.split("Error Message:")[-1]
299
+ .split("ERROR ANALYSIS:")[0]
300
+ .strip()
301
+ )
302
+ error_type = error_message.split(": ")[0]
303
+
304
+ final_params = base_params
305
+ final_params["error_message"] = error_message
306
+ final_params["error_type"] = error_type
307
+
308
+ log("mito_ai_smart_debug_success", params=final_params, key_type=key_type, thread_id=thread_id)
309
+ elif message_type == MessageType.CODE_EXPLAIN:
310
+ final_params = base_params
311
+
312
+ log("mito_ai_code_explain_success", params=final_params, key_type=key_type, thread_id=thread_id)
313
+ elif message_type == MessageType.CHAT:
314
+ final_params = base_params
315
+
316
+ # Chunk the user input
317
+ user_input_chunks = chunk_param(user_input, "user_input")
318
+
319
+ for chunk_key, chunk_value in user_input_chunks.items():
320
+ final_params[chunk_key] = chunk_value
321
+
322
+ log("mito_ai_chat_success", params=final_params, key_type=key_type, thread_id=thread_id)
323
+ elif message_type == MessageType.AGENT_EXECUTION:
324
+ final_params = base_params
325
+
326
+ # Chunk the user input
327
+ user_input_chunks = chunk_param(user_input, "user_input")
328
+
329
+ for chunk_key, chunk_value in user_input_chunks.items():
330
+ final_params[chunk_key] = chunk_value
331
+
332
+ # If the user input is not empty, then this is the user giving the agent a new task
333
+ new_user_input = 'True' if len(user_input_chunks) > 0 else 'False'
334
+ final_params["new_user_input"] = new_user_input
335
+
336
+ log("mito_ai_agent_execution_success", params=final_params, key_type=key_type, thread_id=thread_id)
337
+ elif message_type == MessageType.INLINE_COMPLETION:
338
+ final_params = base_params
339
+ log("mito_ai_inline_completion_success", params=final_params, key_type=key_type, thread_id=thread_id)
340
+ elif message_type == MessageType.AGENT_AUTO_ERROR_FIXUP:
341
+ final_params = base_params
342
+ log("mito_ai_agent_auto_error_fixup_success", params=final_params, key_type=key_type, thread_id=thread_id)
343
+ else:
344
+ final_params = base_params
345
+ final_params["note"] = (
346
+ "This input_location has not been accounted for in `telemetry_utils.py`."
347
+ )
348
+
349
+ log(f"mito_ai_{message_type.value}_success", params=final_params, key_type=key_type, thread_id=thread_id)
350
+
351
+ def log_db_connection_attempt(connection_type: str) -> None:
352
+ log("mito_ai_db_connection_attempt", params={"connection_type": connection_type})
353
+
354
+ def log_db_connection_success(connection_type: str, schema: Dict[str, Any]) -> None:
355
+ log(
356
+ "mito_ai_db_connection_success",
357
+ params={
358
+ "connection_type": connection_type,
359
+ },
360
+ )
361
+
362
+ def log_db_connection_error(connection_type: str, error_message: str) -> None:
363
+ log(
364
+ "mito_ai_db_connection_error",
365
+ params={
366
+ "connection_type": connection_type,
367
+ "error_message": error_message,
368
+ }
369
+ )
370
+
371
+ def log_file_upload_attempt(
372
+ filename: str, file_extension: str, is_chunked: bool, total_chunks: int
373
+ ) -> None:
374
+ log(
375
+ "mito_ai_file_upload_attempt",
376
+ params={
377
+ "filename": filename,
378
+ "file_extension": file_extension,
379
+ "is_chunked": is_chunked,
380
+ "total_chunks": total_chunks,
381
+ },
382
+ )
383
+
384
+ def log_file_upload_failure(error: str) -> None:
385
+ log("mito_ai_file_upload_failure", params={"error_message": error})
386
+
387
+ def log_ai_completion_retry(key_type: Literal['mito_server_key', 'user_key'], thread_id: str, message_type: MessageType, error: BaseException) -> None:
388
+ log(MITO_AI_COMPLETION_RETRY, params={KEY_TYPE_PARAM: key_type, "message_type": message_type.value}, thread_id=thread_id, key_type=key_type, error=error)
389
+
390
+ def log_ai_completion_error(
391
+ key_type: Literal['mito_server_key', 'user_key'],
392
+ thread_id: str,
393
+ message_type: MessageType,
394
+ error: BaseException
395
+ ) -> None:
396
+ log(MITO_AI_COMPLETION_ERROR, params={KEY_TYPE_PARAM: key_type, "message_type": message_type.value}, thread_id=thread_id, key_type=key_type, error=error)
397
+
398
+ def log_mito_server_free_tier_limit_reached(key_type: Literal['mito_server_key', 'user_key'], message_type: MessageType) -> None:
399
+ log(MITO_SERVER_FREE_TIER_LIMIT_REACHED, params={KEY_TYPE_PARAM: key_type, "message_type": message_type.value}, key_type=key_type)
400
+
401
+
402
+ #################################
403
+ # Streamlit Logs
404
+ #################################
405
+
406
+ ###
407
+ # Converting Notebook into Streamlit App code
408
+ ###
409
+
410
+ def log_streamlit_app_conversion_success(key_type: Literal['mito_server_key', 'user_key'], message_type: MessageType, edit_prompt: str) -> None:
411
+ log(
412
+ "mito_ai_streamlit_app_conversion_success",
413
+ key_type=key_type,
414
+ params={
415
+ "edit_prompt": edit_prompt,
416
+ "message_type": message_type.value
417
+ }
418
+ )
419
+
420
+ def log_streamlit_app_validation_retry(key_type: Literal['mito_server_key', 'user_key'], message_type: MessageType, error: List[str]) -> None:
421
+ log(
422
+ "mito_ai_streamlit_app_conversion_retry",
423
+ params={
424
+ "error_message": error,
425
+ "message_type": message_type.value
426
+ },
427
+ key_type=key_type
428
+ )
429
+
430
+ def log_streamlit_app_conversion_error(key_type: Literal['mito_server_key', 'user_key'], message_type: MessageType, error_message: str, formatted_traceback: str, edit_prompt: str) -> None:
431
+ log(
432
+ "mito_ai_streamlit_app_conversion_error",
433
+ params={
434
+ "error_message": error_message,
435
+ "traceback": formatted_traceback,
436
+ "edit_prompt": edit_prompt,
437
+ "message_type": message_type.value
438
+ },
439
+ key_type=key_type
440
+ )
441
+
442
+ ###
443
+ # Setting up Preview
444
+ ###
445
+
446
+ def log_streamlit_app_preview_success(key_type: Literal['mito_server_key', 'user_key'], message_type: MessageType, edit_prompt: str) -> None:
447
+ log(
448
+ "mito_ai_streamlit_app_preview_success",
449
+ key_type=key_type,
450
+ params={
451
+ "edit_prompt": edit_prompt,
452
+ "message_type": message_type.value
453
+ }
454
+ )
455
+
456
+ def log_streamlit_app_preview_failure(key_type: Literal['mito_server_key', 'user_key'], message_type: MessageType, error_message: str, formatted_traceback: str, edit_prompt: str) -> None:
457
+ log(
458
+ "mito_ai_streamlit_app_preview_failure",
459
+ key_type=key_type,
460
+ params={
461
+ "error_message": error_message,
462
+ "traceback": formatted_traceback,
463
+ "message_type": message_type.value,
464
+ "edit_prompt": edit_prompt
465
+ }
466
+ )
467
+
468
+ ###
469
+ # Deploying Streamlit App
470
+ ###
471
+
472
+ def log_streamlit_app_deployment_failure(key_type: Literal['mito_server_key', 'user_key'], message_type: MessageType, error: Dict) -> None:
473
+ log(
474
+ "mito_ai_streamlit_app_deployment_failure",
475
+ key_type=key_type,
476
+ params={
477
+ "error": error, # Contains all details in app_deploy.models.AppDeployError class
478
+ "message_type": message_type.value
479
+ }
480
+ )