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,168 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ import anthropic
5
+ from typing import Any, Dict, List, Optional, Union, AsyncGenerator, Tuple, Callable
6
+ from anthropic.types import MessageParam, TextBlockParam, ToolUnionParam
7
+ from mito_ai.utils.mito_server_utils import get_response_from_mito_server, stream_response_from_mito_server
8
+ from mito_ai.utils.provider_utils import does_message_require_fast_model
9
+ from mito_ai.completions.models import AgentResponse, MessageType, ResponseFormatInfo, CompletionReply, CompletionStreamChunk
10
+ from mito_ai.utils.schema import UJ_STATIC_USER_ID, UJ_USER_EMAIL
11
+ from mito_ai.utils.db import get_user_field
12
+ from mito_ai.constants import MITO_ANTHROPIC_URL
13
+
14
+ __user_email: Optional[str] = None
15
+ __user_id: Optional[str] = None
16
+
17
+ ANTHROPIC_TIMEOUT = 60
18
+ max_retries = 1
19
+
20
+ FAST_ANTHROPIC_MODEL = "claude-3-5-haiku-latest"
21
+
22
+ def _prepare_anthropic_request_data_and_headers(
23
+ model: Union[str, None],
24
+ max_tokens: int,
25
+ temperature: float,
26
+ system: Union[str, List[TextBlockParam], anthropic.Omit],
27
+ messages: List[MessageParam],
28
+ message_type: MessageType,
29
+ tools: Optional[List[ToolUnionParam]],
30
+ tool_choice: Optional[dict],
31
+ stream: Optional[bool]
32
+ ) -> Tuple[Dict[str, Any], Dict[str, str]]:
33
+
34
+ global __user_email, __user_id
35
+ if __user_email is None:
36
+ __user_email = get_user_field(UJ_USER_EMAIL)
37
+ if __user_id is None:
38
+ __user_id = get_user_field(UJ_STATIC_USER_ID)
39
+ # Build the inner data dict (excluding timeout, max_retries, email, user_id)
40
+ inner_data: Dict[str, Any] = {
41
+ "model": model,
42
+ "max_tokens": max_tokens,
43
+ "temperature": temperature,
44
+ "messages": messages,
45
+ "betas": ["context-1m-2025-08-07"]
46
+ }
47
+ # Add system to inner_data only if it is not anthropic.Omit
48
+ if not isinstance(system, anthropic.Omit):
49
+ inner_data["system"] = system
50
+ if tools:
51
+ inner_data["tools"] = tools
52
+ if tool_choice:
53
+ inner_data["tool_choice"] = tool_choice
54
+ if stream:
55
+ inner_data["stream"] = stream
56
+ # Compose the outer data dict
57
+ data = {
58
+ "timeout": ANTHROPIC_TIMEOUT,
59
+ "max_retries": max_retries,
60
+ "email": __user_email,
61
+ "user_id": __user_id,
62
+ "data": inner_data
63
+ }
64
+ headers = {"Content-Type": "application/json"}
65
+ return data, headers
66
+
67
+ async def get_anthropic_completion_from_mito_server(
68
+ model: Union[str, None],
69
+ max_tokens: int,
70
+ temperature: float,
71
+ system: Union[str, anthropic.Omit],
72
+ messages: List[MessageParam],
73
+ tools: Optional[List[ToolUnionParam]],
74
+ tool_choice: Optional[dict],
75
+ message_type: MessageType
76
+ ) -> str:
77
+ data, headers = _prepare_anthropic_request_data_and_headers(
78
+ model, max_tokens, temperature, system, messages, message_type, tools, tool_choice, None
79
+ )
80
+
81
+ return await get_response_from_mito_server(
82
+ MITO_ANTHROPIC_URL,
83
+ headers,
84
+ data,
85
+ ANTHROPIC_TIMEOUT,
86
+ max_retries,
87
+ message_type,
88
+ provider_name="Claude"
89
+ )
90
+
91
+ async def stream_anthropic_completion_from_mito_server(
92
+ model: Union[str, None],
93
+ max_tokens: int,
94
+ temperature: float,
95
+ system: Union[str, List[TextBlockParam], anthropic.Omit],
96
+ messages: List[MessageParam],
97
+ stream: bool,
98
+ message_type: MessageType,
99
+ reply_fn: Optional[Callable[[Union[CompletionReply, CompletionStreamChunk]], None]] = None,
100
+ message_id: Optional[str] = None,
101
+ ) -> AsyncGenerator[str, None]:
102
+ data, headers = _prepare_anthropic_request_data_and_headers(
103
+ model, max_tokens, temperature, system, messages, message_type, None, None, stream
104
+ )
105
+
106
+ # Use the unified streaming function
107
+ # If the reply_fn and message_id are empty, this function still handles those requests. This is particularly needed for the streamlit dashboard functionality
108
+ actual_reply_fn = reply_fn if reply_fn is not None else (lambda x: None)
109
+ actual_message_id = message_id if message_id is not None else ""
110
+ async for chunk in stream_response_from_mito_server(
111
+ url=MITO_ANTHROPIC_URL,
112
+ headers=headers,
113
+ data=data,
114
+ timeout=ANTHROPIC_TIMEOUT,
115
+ max_retries=max_retries,
116
+ message_type=message_type,
117
+ reply_fn=actual_reply_fn,
118
+ message_id=actual_message_id,
119
+ chunk_processor=None,
120
+ provider_name="Claude",
121
+ ):
122
+ yield chunk
123
+
124
+
125
+ def get_anthropic_completion_function_params(
126
+ message_type: MessageType,
127
+ model: str,
128
+ messages: List[MessageParam],
129
+ max_tokens: int,
130
+ system: Union[str, List[TextBlockParam], anthropic.Omit],
131
+ temperature: float = 0.0,
132
+ tools: Optional[List[ToolUnionParam]] = None,
133
+ tool_choice: Optional[dict] = None,
134
+ stream: Optional[bool] = None,
135
+ response_format_info: Optional[ResponseFormatInfo] = None,
136
+ ) -> Dict[str, Any]:
137
+ """
138
+ Build the provider_data dict for Anthropic completions, mirroring the OpenAI approach.
139
+ Only includes fields needed for the Anthropic API.
140
+ """
141
+
142
+ message_requires_fast_model = does_message_require_fast_model(message_type)
143
+ model = FAST_ANTHROPIC_MODEL if message_requires_fast_model else model
144
+
145
+ provider_data = {
146
+ "model": model,
147
+ "max_tokens": max_tokens,
148
+ "temperature": temperature,
149
+ "messages": messages,
150
+ "system": system,
151
+ }
152
+ if tools:
153
+ provider_data["tools"] = tools
154
+ if response_format_info and response_format_info.name == "agent_response":
155
+ provider_data["tools"] = [{
156
+ "name": "agent_response",
157
+ "description": "Output a structured response following the AgentResponse format",
158
+ "input_schema": AgentResponse.model_json_schema()
159
+ }]
160
+ provider_data["tool_choice"] = {"type": "tool", "name": "agent_response"}
161
+
162
+
163
+ if tool_choice:
164
+ provider_data["tool_choice"] = tool_choice
165
+ if stream is not None:
166
+ provider_data["stream"] = stream
167
+ # Optionally handle response_format_info if Anthropic supports it in the future
168
+ return provider_data
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env python
2
+ # coding: utf-8
3
+
4
+ # Copyright (c) Saga Inc.
5
+ # Distributed under the terms of the GPL License.
6
+
7
+ """
8
+ Utilities for creating and initializing the user.json
9
+ file with the current schema
10
+ """
11
+
12
+ import json
13
+ import os
14
+ from typing import Literal, Optional
15
+
16
+ from mito_ai.utils.db import (MITO_FOLDER, USER_JSON_PATH, set_user_field)
17
+ from mito_ai.utils.schema import (GITHUB_ACTION_EMAIL, GITHUB_ACTION_ID,
18
+ UJ_STATIC_USER_ID, UJ_USER_EMAIL,
19
+ USER_JSON_DEFAULT)
20
+ from mito_ai.utils.telemetry_utils import identify
21
+ from mito_ai.utils.utils import is_running_test
22
+
23
+
24
+ def is_user_json_exists_and_valid_json() -> bool:
25
+ """
26
+ Helper function that determines if the current user.json both
27
+ exists and is valid json
28
+ """
29
+ if not os.path.exists(USER_JSON_PATH):
30
+ return False
31
+
32
+ try:
33
+ with open(USER_JSON_PATH, 'r') as f:
34
+ json.loads(f.read())
35
+ return True
36
+ except:
37
+ return False
38
+
39
+ def get_temp_user_id() -> Optional[str]:
40
+ """
41
+ Looks for a temporary user ID, generated by the desktop app.
42
+ """
43
+ temp_user_id_path = os.path.join(MITO_FOLDER, 'temp_user_id.txt')
44
+
45
+ if os.path.exists(temp_user_id_path):
46
+ with open(temp_user_id_path, 'r') as f:
47
+ return f.read()
48
+
49
+ return None
50
+
51
+ def try_create_user_json_file() -> None:
52
+
53
+ # Create the mito folder if it does not exist
54
+ if not os.path.exists(MITO_FOLDER):
55
+ os.mkdir(MITO_FOLDER)
56
+
57
+ # We create a user.json file if it does not exist, or if it
58
+ # is invalid (e.g. it is not parseable JSON).
59
+ if not is_user_json_exists_and_valid_json():
60
+ # First, we write an empty default object
61
+ with open(USER_JSON_PATH, 'w+') as f:
62
+ f.write(json.dumps(USER_JSON_DEFAULT))
63
+
64
+ # Next, look for a temp user id
65
+ temp_user_id = get_temp_user_id()
66
+ if temp_user_id:
67
+ set_user_field(UJ_STATIC_USER_ID, temp_user_id)
68
+
69
+ # Finally, we take special care to put all the testing/CI environments
70
+ # (e.g. Github actions) under one ID and email
71
+ if is_running_test():
72
+ set_user_field(UJ_STATIC_USER_ID, GITHUB_ACTION_ID)
73
+ set_user_field(UJ_USER_EMAIL, GITHUB_ACTION_EMAIL)
74
+
75
+
76
+ def initialize_user() -> None:
77
+ """
78
+ Internal helper function that gets called whenever a ai completion is requested.
79
+
80
+ It:
81
+ 1. Creates the user.json if it does not exist (though it usually does, from the installer)
82
+ 2. Identifies the user
83
+ """
84
+ # Try to create the user.json file, if it does not already exist
85
+ try_create_user_json_file()
86
+
87
+ # Identify the user
88
+ identify()
89
+
90
+ # Note, its possible that a user has a previous version of the user.json file if they
91
+ # downloaded Mito >1 year ago and have not yet upgraded. In this case, we would like to
92
+ # upgrade the user.json to the newest versio. But for simplicity, let's not
93
+ # try to upgrade it here. We will either move away from user.json all together
94
+ # or unify the utilities for mitosheet and mitoai.
mito_ai/utils/db.py ADDED
@@ -0,0 +1,74 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ """
5
+ Helpers for accessing the user.json file
6
+ """
7
+ import os
8
+ import json
9
+ from typing import Any, Optional, Final
10
+ from mito_ai.utils.schema import (
11
+ MITO_FOLDER,
12
+ UJ_AI_MITO_API_NUM_USAGES,
13
+ UJ_MITO_AI_FIRST_USAGE_DATE,
14
+ UJ_MITO_AI_LAST_RESET_DATE,
15
+ UJ_AI_MITO_AUTOCOMPLETE_NUM_USAGES
16
+ )
17
+
18
+ # The path of the user.json file
19
+ USER_JSON_PATH: Final[str] = os.path.join(MITO_FOLDER, 'user.json')
20
+
21
+
22
+ def get_user_field(field: str) -> Optional[Any]:
23
+ """
24
+ Returns the value stored at field in the user.json file,
25
+ but may read a different file if it passed
26
+ """
27
+ try:
28
+ with open(USER_JSON_PATH) as f:
29
+ return json.load(f)[field]
30
+ except:
31
+ return None
32
+
33
+ def set_user_field(field: str, value: Any) -> None:
34
+ """
35
+ Updates the value of a specific field in user.json
36
+ """
37
+ with open(USER_JSON_PATH, 'r') as user_file_old:
38
+ old_user_json = json.load(user_file_old)
39
+ old_user_json[field] = value
40
+ with open(USER_JSON_PATH, 'w+') as f:
41
+ f.write(json.dumps(old_user_json))
42
+
43
+ def get_chat_completion_count() -> int:
44
+ """
45
+ Returns the number of AI completions the user has made in the current 30-day period.
46
+
47
+ Using this helper function lets us mock the number of completions in tests.
48
+ """
49
+ return get_user_field(UJ_AI_MITO_API_NUM_USAGES) or 0
50
+
51
+ def get_autocomplete_count() -> int:
52
+ """
53
+ Returns the number of autocomplete completions the user has made in the current 30-day period.
54
+
55
+ Using this helper function lets us mock the number of autocomplete completions in tests.
56
+ """
57
+ return get_user_field(UJ_AI_MITO_AUTOCOMPLETE_NUM_USAGES) or 0
58
+
59
+ def get_first_completion_date() -> Optional[str]:
60
+ """
61
+ Returns the date of the user's last completion in YYYY-MM-DD format.
62
+
63
+ Using this helper function lets us mock the date of the first completion in tests.
64
+ """
65
+ return get_user_field(UJ_MITO_AI_FIRST_USAGE_DATE) or None
66
+
67
+ def get_last_reset_date() -> Optional[str]:
68
+ """
69
+ Returns the date of the user's last reset in YYYY-MM-DD format.
70
+ Both chat completions and autocomplete share the same reset date.
71
+
72
+ Using this helper function lets us mock the date of the last reset in tests.
73
+ """
74
+ return get_user_field(UJ_MITO_AI_LAST_RESET_DATE) or None
@@ -0,0 +1,42 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ from mito_ai.app_deploy.models import AppDeployError
5
+
6
+ class MitoAppError(Exception):
7
+ """Exception raised for custom error in the application."""
8
+
9
+ def __init__(self, message: str, error_code: int) -> None:
10
+ super().__init__(message)
11
+ self.message = message
12
+ self.error_code = error_code
13
+
14
+ class StreamlitPreviewError(MitoAppError):
15
+ def __str__(self) -> str:
16
+ return f"[PreviewError]: {self.message} (Error Code: {self.error_code})"
17
+
18
+ class StreamlitConversionError(MitoAppError):
19
+ def __str__(self) -> str:
20
+ return f"[ConversionError]: {self.message} (Error Code: {self.error_code})"
21
+
22
+
23
+ class StreamlitDeploymentError(MitoAppError):
24
+ """Raised when a deployment operation fails."""
25
+
26
+ def __init__(self, error: AppDeployError):
27
+ self.error = error
28
+ self.error_type = error.error_type
29
+ self.message_id = getattr(error, "message_id", "ErrorMessageID")
30
+ self.error_code = getattr(error, "error_code", 500)
31
+ self.hint = getattr(error, "hint", "")
32
+ self.traceback = getattr(error, "traceback", "")
33
+ self.error_type = getattr(error, "error_type", "Error")
34
+ self.message = error.message
35
+ print(f"self_message: {self.message}")
36
+ super().__init__(self.message, self.error_code)
37
+
38
+ def __str__(self) -> str:
39
+ base = f"[DeploymentError]: {self.message} (Error Code: {self.error_code})"
40
+ if self.hint:
41
+ base += f"\nHint: {self.hint}"
42
+ return base
@@ -0,0 +1,133 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ import asyncio
5
+ import json
6
+ import time
7
+ from typing import Any, Dict, List, Optional, Callable, Union, AsyncGenerator, Tuple
8
+ from mito_ai.utils.mito_server_utils import get_response_from_mito_server, stream_response_from_mito_server
9
+ from mito_ai.completions.models import AgentResponse, CompletionReply, CompletionStreamChunk, CompletionItem, MessageType
10
+ from mito_ai.constants import MITO_GEMINI_URL
11
+ from mito_ai.utils.provider_utils import does_message_require_fast_model
12
+ from mito_ai.utils.utils import _create_http_client
13
+
14
+ timeout = 30
15
+ max_retries = 1
16
+
17
+ FAST_GEMINI_MODEL = "gemini-2.0-flash-lite"
18
+
19
+ def _prepare_gemini_request_data_and_headers(
20
+ model: str,
21
+ contents: List[Dict[str, Any]],
22
+ message_type: MessageType,
23
+ config: Optional[Dict[str, Any]] = None,
24
+ response_format_info: Optional[Any] = None,
25
+ stream: bool = False
26
+ ) -> Tuple[Dict[str, Any], Dict[str, str]]:
27
+
28
+ inner_data: Dict[str, Any] = {
29
+ "model": model,
30
+ "contents": contents,
31
+ "message_type": message_type.value if hasattr(message_type, 'value') else str(message_type),
32
+ }
33
+
34
+ if response_format_info:
35
+ # Ensure the format is a string, not a class
36
+ format_value = getattr(response_format_info, 'format', None)
37
+ if isinstance(format_value, type):
38
+ format_value = format_value.__name__
39
+ inner_data["response_format_info"] = json.dumps({
40
+ "name": getattr(response_format_info, 'name', None),
41
+ "format": format_value
42
+ })
43
+
44
+ if stream:
45
+ inner_data["stream"] = True
46
+
47
+ if config:
48
+ # Ensure config is serializable
49
+ inner_data["config"] = json.loads(json.dumps(config))
50
+
51
+ data = {
52
+ "timeout": timeout,
53
+ "max_retries": max_retries,
54
+ "data": inner_data
55
+ }
56
+
57
+ headers = {"Content-Type": "application/json"}
58
+ return data, headers
59
+
60
+ async def get_gemini_completion_from_mito_server(
61
+ model: str,
62
+ contents: List[Dict[str, Any]],
63
+ message_type: MessageType,
64
+ config: Optional[Dict[str, Any]] = None,
65
+ response_format_info: Optional[Any] = None
66
+ ) -> str:
67
+ data, headers = _prepare_gemini_request_data_and_headers(model, contents, message_type, config, response_format_info, stream=False)
68
+ return await get_response_from_mito_server(
69
+ MITO_GEMINI_URL,
70
+ headers,
71
+ data,
72
+ timeout,
73
+ max_retries,
74
+ message_type,
75
+ provider_name="Gemini"
76
+ )
77
+
78
+ async def stream_gemini_completion_from_mito_server(
79
+ model: str,
80
+ contents: List[Dict[str, Any]],
81
+ message_type: MessageType,
82
+ message_id: str,
83
+ reply_fn: Callable[[Union[CompletionReply, CompletionStreamChunk]], None]
84
+ ) -> AsyncGenerator[str, None]:
85
+ data, headers = _prepare_gemini_request_data_and_headers(model, contents, message_type, stream=True)
86
+
87
+ # Define chunk processor for Gemini's special processing
88
+ def gemini_chunk_processor(chunk: str) -> str:
89
+ clean_chunk = chunk.strip('"')
90
+ return clean_chunk.encode().decode('unicode_escape')
91
+
92
+ # Use the unified streaming function with Gemini's chunk processor
93
+ async for chunk in stream_response_from_mito_server(
94
+ url=MITO_GEMINI_URL,
95
+ headers=headers,
96
+ data=data,
97
+ timeout=timeout,
98
+ max_retries=max_retries,
99
+ message_type=message_type,
100
+ reply_fn=reply_fn,
101
+ message_id=message_id,
102
+ chunk_processor=gemini_chunk_processor,
103
+ provider_name="Gemini",
104
+ ):
105
+ yield chunk
106
+
107
+ def get_gemini_completion_function_params(
108
+ message_type: MessageType,
109
+ model: str,
110
+ contents: list[dict[str, Any]],
111
+ response_format_info: Optional[Any] = None,
112
+ ) -> Dict[str, Any]:
113
+ """
114
+ Build the provider_data dict for Gemini completions, mirroring the OpenAI/Anthropic approach.
115
+ Only includes fields needed for the Gemini API.
116
+ """
117
+ message_requires_fast_model = does_message_require_fast_model(message_type)
118
+ model = FAST_GEMINI_MODEL if message_requires_fast_model else model
119
+
120
+ provider_data: Dict[str, Any] = {
121
+ "model": model,
122
+ "contents": contents,
123
+ "message_type": message_type.value if hasattr(message_type, 'value') else str(message_type),
124
+ }
125
+
126
+ # Configure response format if provided
127
+ if response_format_info:
128
+ provider_data["config"] = {
129
+ "response_mime_type": "application/json",
130
+ "response_schema": AgentResponse.model_json_schema()
131
+ }
132
+
133
+ return provider_data
@@ -0,0 +1,87 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ import re
5
+ from typing import List
6
+ from mito_ai.constants import MESSAGE_HISTORY_TRIM_THRESHOLD
7
+ from openai.types.chat import ChatCompletionMessageParam
8
+ from mito_ai.completions.prompt_builders.prompt_constants import (
9
+ ACTIVE_CELL_ID_SECTION_HEADING,
10
+ ACTIVE_CELL_OUTPUT_SECTION_HEADING,
11
+ GET_CELL_OUTPUT_TOOL_RESPONSE_SECTION_HEADING,
12
+ FILES_SECTION_HEADING,
13
+ STREAMLIT_APP_STATUS_SECTION_HEADING,
14
+ VARIABLES_SECTION_HEADING,
15
+ JUPYTER_NOTEBOOK_SECTION_HEADING,
16
+ CONTENT_REMOVED_PLACEHOLDER
17
+ )
18
+
19
+
20
+ def trim_sections_from_message_content(content: str) -> str:
21
+ """
22
+ Removes specific metadata sections from message content to reduce token count so
23
+ that users don't exceed the token limit for the LLM.
24
+
25
+ These sections are replaced with a placeholder text.
26
+ """
27
+
28
+ # Replace metadata sections with placeholders
29
+ section_headings = [
30
+ FILES_SECTION_HEADING,
31
+ VARIABLES_SECTION_HEADING,
32
+ JUPYTER_NOTEBOOK_SECTION_HEADING,
33
+ GET_CELL_OUTPUT_TOOL_RESPONSE_SECTION_HEADING,
34
+ ACTIVE_CELL_OUTPUT_SECTION_HEADING,
35
+ ACTIVE_CELL_ID_SECTION_HEADING,
36
+ STREAMLIT_APP_STATUS_SECTION_HEADING
37
+ ]
38
+
39
+ for heading in section_headings:
40
+ content = re.sub(
41
+ f"{re.escape(heading)}\n(?:.+\n)+",
42
+ f"{heading} {CONTENT_REMOVED_PLACEHOLDER}\n",
43
+ content,
44
+ )
45
+
46
+ return content
47
+
48
+
49
+ def trim_old_messages(messages: List[ChatCompletionMessageParam]) -> List[ChatCompletionMessageParam]:
50
+ """
51
+ Trims metadata sections from messages that are older than the specified number of recent messages.
52
+ We do this in order to reduce the token count of the messages, which helps us stay under the token limit for the LLM.
53
+ """
54
+ if len(messages) <= MESSAGE_HISTORY_TRIM_THRESHOLD:
55
+ return messages
56
+
57
+ # Process all messages except the keep_recent most recent ones.
58
+ # Only trim user messages, which is where this metadata lives.
59
+ # We want to not edit the system messages, as they contain important information / examples.
60
+ for i in range(len(messages) - MESSAGE_HISTORY_TRIM_THRESHOLD):
61
+ content = messages[i].get("content")
62
+
63
+ is_user_message = messages[i].get("role") == "user"
64
+ if not is_user_message:
65
+ continue
66
+
67
+ content = messages[i].get("content")
68
+
69
+ if content is None:
70
+ continue
71
+
72
+ if isinstance(content, str):
73
+ # If content is just a string, then we just trim the metadata sections
74
+ messages[i]["content"] = trim_sections_from_message_content(content)
75
+ else:
76
+ # Otherwise, we get rid of the image_url section and just keep the trimmed text
77
+ # We assume that there is only one text section in the content
78
+ text_content = ""
79
+ for section in content:
80
+ if section.get("type") == "text" and "text" in section:
81
+ text_content = section["text"] #type: ignore
82
+ break
83
+
84
+ messages[i]["content"] = trim_sections_from_message_content(text_content)
85
+
86
+
87
+ return messages