mito-ai 0.1.33__py3-none-any.whl → 0.1.49__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 (146) hide show
  1. mito_ai/__init__.py +49 -9
  2. mito_ai/_version.py +1 -1
  3. mito_ai/anthropic_client.py +142 -67
  4. mito_ai/{app_builder → app_deploy}/__init__.py +1 -1
  5. mito_ai/app_deploy/app_deploy_utils.py +44 -0
  6. mito_ai/app_deploy/handlers.py +345 -0
  7. mito_ai/{app_builder → app_deploy}/models.py +35 -22
  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/agent_execution_handler.py +1 -1
  19. mito_ai/completions/completion_handlers/chat_completion_handler.py +4 -4
  20. mito_ai/completions/completion_handlers/utils.py +99 -37
  21. mito_ai/completions/handlers.py +57 -20
  22. mito_ai/completions/message_history.py +9 -1
  23. mito_ai/completions/models.py +31 -7
  24. mito_ai/completions/prompt_builders/agent_execution_prompt.py +21 -2
  25. mito_ai/completions/prompt_builders/agent_smart_debug_prompt.py +8 -0
  26. mito_ai/completions/prompt_builders/agent_system_message.py +115 -42
  27. mito_ai/completions/prompt_builders/chat_name_prompt.py +6 -6
  28. mito_ai/completions/prompt_builders/chat_prompt.py +18 -11
  29. mito_ai/completions/prompt_builders/chat_system_message.py +4 -0
  30. mito_ai/completions/prompt_builders/prompt_constants.py +23 -4
  31. mito_ai/completions/prompt_builders/utils.py +72 -10
  32. mito_ai/completions/providers.py +81 -47
  33. mito_ai/constants.py +25 -24
  34. mito_ai/file_uploads/__init__.py +3 -0
  35. mito_ai/file_uploads/handlers.py +248 -0
  36. mito_ai/file_uploads/urls.py +21 -0
  37. mito_ai/gemini_client.py +44 -48
  38. mito_ai/log/handlers.py +10 -3
  39. mito_ai/log/urls.py +3 -3
  40. mito_ai/openai_client.py +30 -44
  41. mito_ai/path_utils.py +70 -0
  42. mito_ai/streamlit_conversion/agent_utils.py +37 -0
  43. mito_ai/streamlit_conversion/prompts/prompt_constants.py +172 -0
  44. mito_ai/streamlit_conversion/prompts/prompt_utils.py +10 -0
  45. mito_ai/streamlit_conversion/prompts/streamlit_app_creation_prompt.py +46 -0
  46. mito_ai/streamlit_conversion/prompts/streamlit_error_correction_prompt.py +28 -0
  47. mito_ai/streamlit_conversion/prompts/streamlit_finish_todo_prompt.py +45 -0
  48. mito_ai/streamlit_conversion/prompts/streamlit_system_prompt.py +56 -0
  49. mito_ai/streamlit_conversion/prompts/update_existing_app_prompt.py +50 -0
  50. mito_ai/streamlit_conversion/search_replace_utils.py +94 -0
  51. mito_ai/streamlit_conversion/streamlit_agent_handler.py +144 -0
  52. mito_ai/streamlit_conversion/streamlit_utils.py +85 -0
  53. mito_ai/streamlit_conversion/validate_streamlit_app.py +105 -0
  54. mito_ai/streamlit_preview/__init__.py +6 -0
  55. mito_ai/streamlit_preview/handlers.py +111 -0
  56. mito_ai/streamlit_preview/manager.py +152 -0
  57. mito_ai/streamlit_preview/urls.py +22 -0
  58. mito_ai/streamlit_preview/utils.py +29 -0
  59. mito_ai/tests/chat_history/test_chat_history.py +211 -0
  60. mito_ai/tests/completions/completion_handlers_utils_test.py +190 -0
  61. mito_ai/tests/deploy_app/test_app_deploy_utils.py +89 -0
  62. mito_ai/tests/file_uploads/__init__.py +2 -0
  63. mito_ai/tests/file_uploads/test_handlers.py +282 -0
  64. mito_ai/tests/message_history/test_generate_short_chat_name.py +0 -4
  65. mito_ai/tests/message_history/test_message_history_utils.py +103 -23
  66. mito_ai/tests/open_ai_utils_test.py +18 -22
  67. mito_ai/tests/providers/test_anthropic_client.py +447 -0
  68. mito_ai/tests/providers/test_azure.py +2 -6
  69. mito_ai/tests/providers/test_capabilities.py +120 -0
  70. mito_ai/tests/{test_gemini_client.py → providers/test_gemini_client.py} +40 -36
  71. mito_ai/tests/providers/test_mito_server_utils.py +448 -0
  72. mito_ai/tests/providers/test_model_resolution.py +130 -0
  73. mito_ai/tests/providers/test_openai_client.py +57 -0
  74. mito_ai/tests/providers/test_provider_completion_exception.py +66 -0
  75. mito_ai/tests/providers/test_provider_limits.py +42 -0
  76. mito_ai/tests/providers/test_providers.py +382 -0
  77. mito_ai/tests/providers/test_retry_logic.py +389 -0
  78. mito_ai/tests/providers/test_stream_mito_server_utils.py +140 -0
  79. mito_ai/tests/providers/utils.py +85 -0
  80. mito_ai/tests/streamlit_conversion/__init__.py +3 -0
  81. mito_ai/tests/streamlit_conversion/test_apply_search_replace.py +240 -0
  82. mito_ai/tests/streamlit_conversion/test_streamlit_agent_handler.py +246 -0
  83. mito_ai/tests/streamlit_conversion/test_streamlit_utils.py +193 -0
  84. mito_ai/tests/streamlit_conversion/test_validate_streamlit_app.py +112 -0
  85. mito_ai/tests/streamlit_preview/test_streamlit_preview_handler.py +118 -0
  86. mito_ai/tests/streamlit_preview/test_streamlit_preview_manager.py +292 -0
  87. mito_ai/tests/test_constants.py +31 -3
  88. mito_ai/tests/test_telemetry.py +12 -0
  89. mito_ai/tests/user/__init__.py +2 -0
  90. mito_ai/tests/user/test_user.py +120 -0
  91. mito_ai/tests/utils/test_anthropic_utils.py +6 -6
  92. mito_ai/user/handlers.py +45 -0
  93. mito_ai/user/urls.py +21 -0
  94. mito_ai/utils/anthropic_utils.py +55 -121
  95. mito_ai/utils/create.py +17 -1
  96. mito_ai/utils/error_classes.py +42 -0
  97. mito_ai/utils/gemini_utils.py +39 -94
  98. mito_ai/utils/message_history_utils.py +7 -4
  99. mito_ai/utils/mito_server_utils.py +242 -0
  100. mito_ai/utils/open_ai_utils.py +38 -155
  101. mito_ai/utils/provider_utils.py +49 -0
  102. mito_ai/utils/server_limits.py +1 -1
  103. mito_ai/utils/telemetry_utils.py +137 -5
  104. {mito_ai-0.1.33.data → mito_ai-0.1.49.data}/data/share/jupyter/labextensions/mito_ai/build_log.json +102 -100
  105. {mito_ai-0.1.33.data → mito_ai-0.1.49.data}/data/share/jupyter/labextensions/mito_ai/package.json +4 -2
  106. {mito_ai-0.1.33.data → mito_ai-0.1.49.data}/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/package.json.orig +3 -1
  107. {mito_ai-0.1.33.data → mito_ai-0.1.49.data}/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/toolbar-buttons.json +2 -2
  108. mito_ai-0.1.33.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.281f4b9af60d620c6fb1.js → mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.8f1845da6bf2b128c049.js +15948 -8403
  109. mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.8f1845da6bf2b128c049.js.map +1 -0
  110. mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/node_modules_process_browser_js.4b128e94d31a81ebd209.js +198 -0
  111. mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/node_modules_process_browser_js.4b128e94d31a81ebd209.js.map +1 -0
  112. mito_ai-0.1.33.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.4f1d00fd0c58fcc05d8d.js → mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.8b24b5b3b93f95205b56.js +58 -33
  113. mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.8b24b5b3b93f95205b56.js.map +1 -0
  114. mito_ai-0.1.33.data/data/share/jupyter/labextensions/mito_ai/static/style_index_js.06083e515de4862df010.js → mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/style_index_js.5876024bb17dbd6a3ee6.js +10 -2
  115. mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/style_index_js.5876024bb17dbd6a3ee6.js.map +1 -0
  116. mito_ai-0.1.49.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
  117. mito_ai-0.1.49.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
  118. mito_ai-0.1.49.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
  119. mito_ai-0.1.49.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
  120. mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_dist_esm_index_mjs.6bac1a8c4cc93f15f6b7.js +1021 -0
  121. mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_dist_esm_index_mjs.6bac1a8c4cc93f15f6b7.js.map +1 -0
  122. mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_ui-react_dist_esm_index_mjs.4fcecd65bef9e9847609.js +59698 -0
  123. mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_ui-react_dist_esm_index_mjs.4fcecd65bef9e9847609.js.map +1 -0
  124. mito_ai-0.1.49.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
  125. mito_ai-0.1.49.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
  126. mito_ai-0.1.33.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.9795f79265ddb416864b.js → mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.3f6754ac5116d47de76b.js +2 -240
  127. mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.3f6754ac5116d47de76b.js.map +1 -0
  128. {mito_ai-0.1.33.dist-info → mito_ai-0.1.49.dist-info}/METADATA +5 -2
  129. mito_ai-0.1.49.dist-info/RECORD +205 -0
  130. mito_ai/app_builder/handlers.py +0 -218
  131. mito_ai/tests/providers_test.py +0 -438
  132. mito_ai/tests/test_anthropic_client.py +0 -270
  133. mito_ai-0.1.33.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.281f4b9af60d620c6fb1.js.map +0 -1
  134. mito_ai-0.1.33.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.4f1d00fd0c58fcc05d8d.js.map +0 -1
  135. mito_ai-0.1.33.data/data/share/jupyter/labextensions/mito_ai/static/style_index_js.06083e515de4862df010.js.map +0 -1
  136. mito_ai-0.1.33.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_html2canvas_dist_html2canvas_js.ea47e8c8c906197f8d19.js +0 -7842
  137. mito_ai-0.1.33.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_html2canvas_dist_html2canvas_js.ea47e8c8c906197f8d19.js.map +0 -1
  138. mito_ai-0.1.33.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.9795f79265ddb416864b.js.map +0 -1
  139. mito_ai-0.1.33.dist-info/RECORD +0 -134
  140. {mito_ai-0.1.33.data → mito_ai-0.1.49.data}/data/etc/jupyter/jupyter_server_config.d/mito_ai.json +0 -0
  141. {mito_ai-0.1.33.data → mito_ai-0.1.49.data}/data/share/jupyter/labextensions/mito_ai/static/style.js +0 -0
  142. {mito_ai-0.1.33.data → mito_ai-0.1.49.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_vscode-diff_dist_index_js.ea55f1f9346638aafbcf.js +0 -0
  143. {mito_ai-0.1.33.data → mito_ai-0.1.49.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_vscode-diff_dist_index_js.ea55f1f9346638aafbcf.js.map +0 -0
  144. {mito_ai-0.1.33.dist-info → mito_ai-0.1.49.dist-info}/WHEEL +0 -0
  145. {mito_ai-0.1.33.dist-info → mito_ai-0.1.49.dist-info}/entry_points.txt +0 -0
  146. {mito_ai-0.1.33.dist-info → mito_ai-0.1.49.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,120 @@
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
+ import requests
7
+ import tempfile
8
+ from unittest.mock import patch
9
+ import pytest
10
+ from mito_ai.tests.conftest import TOKEN
11
+
12
+
13
+ @pytest.fixture
14
+ def mock_user_json():
15
+ """Fixture that creates a temporary user.json file with test data"""
16
+ with tempfile.TemporaryDirectory() as temp_dir:
17
+ # Create the .mito directory
18
+ mito_dir = os.path.join(temp_dir, ".mito")
19
+ os.makedirs(mito_dir, exist_ok=True)
20
+
21
+ # Create a user.json file with test data
22
+ user_json_path = os.path.join(mito_dir, "user.json")
23
+ user_data = {
24
+ "user_email": "test@mail.com",
25
+ "static_user_id": "test_user_123",
26
+ }
27
+
28
+ with open(user_json_path, "w") as f:
29
+ json.dump(user_data, f)
30
+
31
+ yield user_json_path
32
+
33
+
34
+ # --- GET USER KEY ---
35
+
36
+
37
+ def test_get_user_with_mocked_data_success(
38
+ jp_base_url: str, mock_user_json: str
39
+ ) -> None:
40
+ """Test successful GET user endpoint with mocked data"""
41
+ with patch("mito_ai.utils.db.USER_JSON_PATH", mock_user_json):
42
+ response = requests.get(
43
+ jp_base_url + f"/mito-ai/user/user_email",
44
+ headers={"Authorization": f"token {TOKEN}"},
45
+ )
46
+ assert response.status_code == 200
47
+
48
+ response_json = response.json()
49
+ assert response_json["key"] == "user_email"
50
+ assert response_json["value"] == "test@mail.com"
51
+
52
+
53
+ def test_get_user_with_mocked_data_not_found(
54
+ jp_base_url: str, mock_user_json: str
55
+ ) -> None:
56
+ """Test GET user endpoint with mocked data for non-existent key"""
57
+ with patch("mito_ai.utils.db.USER_JSON_PATH", mock_user_json):
58
+ response = requests.get(
59
+ jp_base_url + "/mito-ai/user/non_existent_key",
60
+ headers={"Authorization": f"token {TOKEN}"},
61
+ )
62
+ assert response.status_code == 404
63
+
64
+ response_json = response.json()
65
+ assert (
66
+ response_json["error"] == "User field with key 'non_existent_key' not found"
67
+ )
68
+
69
+
70
+ def test_get_user_with_no_auth(jp_base_url: str) -> None:
71
+ response = requests.get(
72
+ jp_base_url + f"/mito-ai/user/user_email",
73
+ )
74
+ assert response.status_code == 403 # Forbidden
75
+
76
+
77
+ def test_get_user_with_incorrect_auth(jp_base_url: str) -> None:
78
+ response = requests.get(
79
+ jp_base_url + f"/mito-ai/user/user_email",
80
+ headers={"Authorization": f"token incorrect-token"},
81
+ )
82
+ assert response.status_code == 403 # Forbidden
83
+
84
+
85
+ # --- PUT USER KEY ---
86
+
87
+
88
+ def test_put_user_with_mocked_data_success(
89
+ jp_base_url: str, mock_user_json: str
90
+ ) -> None:
91
+ """Test successful PUT user endpoint with mocked data"""
92
+ with patch("mito_ai.utils.db.USER_JSON_PATH", mock_user_json):
93
+ response = requests.put(
94
+ jp_base_url + f"/mito-ai/user/user_email",
95
+ headers={"Authorization": f"token {TOKEN}"},
96
+ json={"value": "jdoe@mail.com"},
97
+ )
98
+ assert response.status_code == 200
99
+
100
+ response_json = response.json()
101
+ assert response_json["status"] == "success"
102
+ assert response_json["key"] == "user_email"
103
+ assert response_json["value"] == "jdoe@mail.com"
104
+
105
+
106
+ def test_put_user_with_no_auth(jp_base_url: str) -> None:
107
+ response = requests.put(
108
+ jp_base_url + f"/mito-ai/user/user_email",
109
+ json={"value": "jdoe@mail.com"},
110
+ )
111
+ assert response.status_code == 403 # Forbidden
112
+
113
+
114
+ def test_put_user_with_incorrect_auth(jp_base_url: str) -> None:
115
+ response = requests.put(
116
+ jp_base_url + f"/mito-ai/user/user_email",
117
+ headers={"Authorization": f"token incorrect-token"},
118
+ json={"value": "jdoe@mail.com"},
119
+ )
120
+ assert response.status_code == 403 # Forbidden
@@ -5,7 +5,7 @@ import pytest
5
5
  import anthropic
6
6
  from typing import List, Dict, Any, Tuple, Union, cast
7
7
  from anthropic.types import MessageParam, ToolUnionParam, ToolParam
8
- from mito_ai.utils.anthropic_utils import _prepare_anthropic_request_data_and_headers
8
+ from mito_ai.utils.anthropic_utils import ANTHROPIC_TIMEOUT, _prepare_anthropic_request_data_and_headers
9
9
  from mito_ai.completions.models import MessageType
10
10
  from mito_ai.utils.schema import UJ_STATIC_USER_ID, UJ_USER_EMAIL
11
11
  from mito_ai.utils.db import get_user_field
@@ -35,7 +35,7 @@ def test_basic_request_preparation():
35
35
  max_tokens = 100
36
36
  temperature = 0.7
37
37
  # Use NotGiven to ensure system is not included in inner_data
38
- system = anthropic.NotGiven()
38
+ system = anthropic.Omit()
39
39
  messages: List[MessageParam] = [{"role": "user", "content": "Hello"}]
40
40
  message_type = MessageType.CHAT
41
41
 
@@ -52,7 +52,7 @@ def test_basic_request_preparation():
52
52
  )
53
53
 
54
54
  assert headers == {"Content-Type": "application/json"}
55
- assert data["timeout"] == 30
55
+ assert data["timeout"] == ANTHROPIC_TIMEOUT
56
56
  assert data["max_retries"] == 1
57
57
  assert data["email"] == "test@example.com"
58
58
  assert data["user_id"] == "test_user_id"
@@ -106,7 +106,7 @@ def test_tools_and_tool_choice():
106
106
  model="claude-3-sonnet",
107
107
  max_tokens=100,
108
108
  temperature=0.7,
109
- system=anthropic.NotGiven(),
109
+ system=anthropic.Omit(),
110
110
  messages=[{"role": "user", "content": "Hello"}],
111
111
  message_type=MessageType.CHAT,
112
112
  tools=tools,
@@ -124,7 +124,7 @@ def test_stream_parameter():
124
124
  model="claude-3-sonnet",
125
125
  max_tokens=100,
126
126
  temperature=0.7,
127
- system=anthropic.NotGiven(),
127
+ system=anthropic.Omit(),
128
128
  messages=[{"role": "user", "content": "Hello"}],
129
129
  message_type=MessageType.CHAT,
130
130
  tools=None,
@@ -150,7 +150,7 @@ def test_missing_user_info(monkeypatch):
150
150
  model="claude-3-sonnet",
151
151
  max_tokens=100,
152
152
  temperature=0.7,
153
- system=anthropic.NotGiven(),
153
+ system=anthropic.Omit(),
154
154
  messages=[{"role": "user", "content": "Hello"}],
155
155
  message_type=MessageType.CHAT,
156
156
  tools=None,
@@ -0,0 +1,45 @@
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 tornado
6
+ from typing import Any, Optional
7
+ from jupyter_server.base.handlers import APIHandler
8
+ from mito_ai.utils.db import get_user_field, set_user_field
9
+ from mito_ai.utils.telemetry_utils import identify
10
+ from mito_ai.utils.version_utils import is_pro
11
+
12
+
13
+ class UserHandler(APIHandler):
14
+ """Handler for operations on a specific user"""
15
+
16
+ @tornado.web.authenticated
17
+ def get(self, key: str) -> None:
18
+ value: Optional[Any] = None
19
+
20
+ if key == "is_pro":
21
+ # Special case, since we don't store this key
22
+ # in the user.json file.
23
+ value = str(is_pro())
24
+ else:
25
+ value = get_user_field(key)
26
+
27
+ if value is None:
28
+ self.set_status(404)
29
+ self.finish(json.dumps({"error": f"User field with key '{key}' not found"}))
30
+ else:
31
+ self.finish(json.dumps({"key": key, "value": value}))
32
+
33
+ @tornado.web.authenticated
34
+ def put(self, key: str) -> None:
35
+ data = json.loads(self.request.body)
36
+ if "value" not in data:
37
+ self.set_status(400)
38
+ self.finish(json.dumps({"error": "Value is required"}))
39
+ return
40
+
41
+ set_user_field(key, data["value"])
42
+ identify() # Log the new user
43
+ self.finish(
44
+ json.dumps({"status": "success", "key": key, "value": data["value"]})
45
+ )
mito_ai/user/urls.py ADDED
@@ -0,0 +1,21 @@
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 Any, List, Tuple
5
+ from jupyter_server.utils import url_path_join
6
+ from mito_ai.user.handlers import UserHandler
7
+
8
+
9
+ def get_user_urls(base_url: str) -> List[Tuple[str, Any, dict]]:
10
+ """Get all user related URL patterns.
11
+
12
+ Args:
13
+ base_url: The base URL for the Jupyter server
14
+
15
+ Returns:
16
+ List of (url_pattern, handler_class, handler_kwargs) tuples
17
+ """
18
+ BASE_URL = base_url + "/mito-ai"
19
+ return [
20
+ (url_path_join(BASE_URL, "user/(.*)"), UserHandler, {}),
21
+ ]
@@ -1,42 +1,36 @@
1
1
  # Copyright (c) Saga Inc.
2
2
  # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
3
 
4
- import asyncio
5
- import json
6
- import time
7
4
  import anthropic
8
- from typing import Any, Dict, List, Optional, Union, AsyncGenerator, Tuple, Callable, cast
9
-
10
- from anthropic.types import MessageParam, Message, TextBlock, ToolUnionParam
11
- from openai.types.chat import ChatCompletionMessageParam
12
- from mito_ai.completions.models import AgentResponse, MessageType, ResponseFormatInfo, CompletionReply, CompletionStreamChunk, CompletionItem
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
13
10
  from mito_ai.utils.schema import UJ_STATIC_USER_ID, UJ_USER_EMAIL
14
11
  from mito_ai.utils.db import get_user_field
15
- from mito_ai.utils.utils import is_running_test
16
- from mito_ai.utils.server_limits import check_mito_server_quota
17
- from .utils import _create_http_client
18
- from tornado.httpclient import AsyncHTTPClient
19
12
  from mito_ai.constants import MITO_ANTHROPIC_URL
20
13
 
21
14
  __user_email: Optional[str] = None
22
15
  __user_id: Optional[str] = None
23
16
 
24
- timeout = 30
17
+ ANTHROPIC_TIMEOUT = 60
25
18
  max_retries = 1
26
- INLINE_COMPLETION_MODEL = "claude-3-5-haiku-latest"
19
+
20
+ FAST_ANTHROPIC_MODEL = "claude-3-5-haiku-latest"
27
21
 
28
22
  def _prepare_anthropic_request_data_and_headers(
29
23
  model: Union[str, None],
30
24
  max_tokens: int,
31
25
  temperature: float,
32
- system: Union[str, anthropic.NotGiven],
26
+ system: Union[str, List[TextBlockParam], anthropic.Omit],
33
27
  messages: List[MessageParam],
34
28
  message_type: MessageType,
35
29
  tools: Optional[List[ToolUnionParam]],
36
30
  tool_choice: Optional[dict],
37
31
  stream: Optional[bool]
38
32
  ) -> Tuple[Dict[str, Any], Dict[str, str]]:
39
- check_mito_server_quota(message_type)
33
+
40
34
  global __user_email, __user_id
41
35
  if __user_email is None:
42
36
  __user_email = get_user_field(UJ_USER_EMAIL)
@@ -47,10 +41,11 @@ def _prepare_anthropic_request_data_and_headers(
47
41
  "model": model,
48
42
  "max_tokens": max_tokens,
49
43
  "temperature": temperature,
50
- "messages": messages
44
+ "messages": messages,
45
+ "betas": ["context-1m-2025-08-07"]
51
46
  }
52
- # Add system to inner_data only if it is not anthropic.NotGiven
53
- if not isinstance(system, anthropic.NotGiven):
47
+ # Add system to inner_data only if it is not anthropic.Omit
48
+ if not isinstance(system, anthropic.Omit):
54
49
  inner_data["system"] = system
55
50
  if tools:
56
51
  inner_data["tools"] = tools
@@ -60,7 +55,7 @@ def _prepare_anthropic_request_data_and_headers(
60
55
  inner_data["stream"] = stream
61
56
  # Compose the outer data dict
62
57
  data = {
63
- "timeout": timeout,
58
+ "timeout": ANTHROPIC_TIMEOUT,
64
59
  "max_retries": max_retries,
65
60
  "email": __user_email,
66
61
  "user_id": __user_id,
@@ -73,42 +68,31 @@ async def get_anthropic_completion_from_mito_server(
73
68
  model: Union[str, None],
74
69
  max_tokens: int,
75
70
  temperature: float,
76
- system: Union[str, anthropic.NotGiven],
71
+ system: Union[str, anthropic.Omit],
77
72
  messages: List[MessageParam],
78
73
  tools: Optional[List[ToolUnionParam]],
79
74
  tool_choice: Optional[dict],
80
75
  message_type: MessageType
81
- ) -> Message:
76
+ ) -> str:
82
77
  data, headers = _prepare_anthropic_request_data_and_headers(
83
78
  model, max_tokens, temperature, system, messages, message_type, tools, tool_choice, None
84
79
  )
85
- http_client, http_client_timeout = _create_http_client(timeout, max_retries)
86
- start_time = time.time()
87
- try:
88
- res = await http_client.fetch(
89
- MITO_ANTHROPIC_URL,
90
- method="POST",
91
- headers=headers,
92
- body=json.dumps(data),
93
- request_timeout=http_client_timeout
94
- )
95
- print(f"Anthropic request completed in {time.time() - start_time:.2f} seconds")
96
- except Exception as e:
97
- print(f"Anthropic request failed after {time.time() - start_time:.2f} seconds with error: {str(e)}")
98
- raise
99
- finally:
100
- http_client.close()
101
- content = json.loads(res.body)
102
- # If the response is wrapped in a 'data' field, extract it
103
- if isinstance(content, dict) and "data" in content:
104
- return cast(Message, content["data"])
105
- return cast(Message, content)
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
+ )
106
90
 
107
91
  async def stream_anthropic_completion_from_mito_server(
108
92
  model: Union[str, None],
109
93
  max_tokens: int,
110
94
  temperature: float,
111
- system: Union[str, anthropic.NotGiven],
95
+ system: Union[str, List[TextBlockParam], anthropic.Omit],
112
96
  messages: List[MessageParam],
113
97
  stream: bool,
114
98
  message_type: MessageType,
@@ -118,91 +102,46 @@ async def stream_anthropic_completion_from_mito_server(
118
102
  data, headers = _prepare_anthropic_request_data_and_headers(
119
103
  model, max_tokens, temperature, system, messages, message_type, None, None, stream
120
104
  )
121
- http_client, http_client_timeout = _create_http_client(timeout, max_retries)
122
- start_time = time.time()
123
- chunk_queue: asyncio.Queue[str] = asyncio.Queue()
124
- fetch_complete = False
125
- def chunk_callback(chunk: bytes) -> None:
126
- try:
127
- chunk_str = chunk.decode('utf-8')
128
- asyncio.create_task(chunk_queue.put(chunk_str))
129
- except Exception as e:
130
- print(f"Error processing Anthropic streaming chunk: {str(e)}")
131
- fetch_future = None
132
- try:
133
- fetch_future = http_client.fetch(
134
- MITO_ANTHROPIC_URL,
135
- method="POST",
136
- headers=headers,
137
- body=json.dumps(data),
138
- request_timeout=http_client_timeout,
139
- streaming_callback=chunk_callback
140
- )
141
- async def wait_for_fetch() -> None:
142
- try:
143
- await fetch_future
144
- nonlocal fetch_complete
145
- fetch_complete = True
146
- print("Anthropic fetch completed")
147
- except Exception as e:
148
- print(f"Error in Anthropic fetch: {str(e)}")
149
- raise
150
- fetch_task = asyncio.create_task(wait_for_fetch())
151
- while not (fetch_complete and chunk_queue.empty()):
152
- try:
153
- chunk = await asyncio.wait_for(chunk_queue.get(), timeout=0.1)
154
- if reply_fn and message_id:
155
- reply_fn(CompletionStreamChunk(
156
- parent_id=message_id,
157
- chunk=CompletionItem(
158
- content=chunk,
159
- isIncomplete=True,
160
- token=message_id,
161
- ),
162
- done=False,
163
- ))
164
- yield chunk
165
- except asyncio.TimeoutError:
166
- if fetch_complete and chunk_queue.empty():
167
- break
168
- continue
169
- print(f"\nAnthropic stream completed in {time.time() - start_time:.2f} seconds")
170
- if reply_fn and message_id:
171
- reply_fn(CompletionStreamChunk(
172
- parent_id=message_id,
173
- chunk=CompletionItem(
174
- content="",
175
- isIncomplete=False,
176
- token=message_id,
177
- ),
178
- done=True,
179
- ))
180
- except Exception as e:
181
- print(f"\nAnthropic stream failed after {time.time() - start_time:.2f} seconds with error: {str(e)}")
182
- if fetch_future:
183
- try:
184
- await fetch_future
185
- except Exception:
186
- pass
187
- raise
188
- finally:
189
- http_client.close()
190
-
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
+
191
125
  def get_anthropic_completion_function_params(
126
+ message_type: MessageType,
192
127
  model: str,
193
128
  messages: List[MessageParam],
194
129
  max_tokens: int,
195
- system: Union[str, anthropic.NotGiven],
130
+ system: Union[str, List[TextBlockParam], anthropic.Omit],
196
131
  temperature: float = 0.0,
197
132
  tools: Optional[List[ToolUnionParam]] = None,
198
133
  tool_choice: Optional[dict] = None,
199
134
  stream: Optional[bool] = None,
200
135
  response_format_info: Optional[ResponseFormatInfo] = None,
201
- ) -> Dict[str, Any]:
136
+ ) -> Dict[str, Any]:
202
137
  """
203
138
  Build the provider_data dict for Anthropic completions, mirroring the OpenAI approach.
204
139
  Only includes fields needed for the Anthropic API.
205
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
+
206
145
  provider_data = {
207
146
  "model": model,
208
147
  "max_tokens": max_tokens,
@@ -210,11 +149,6 @@ def get_anthropic_completion_function_params(
210
149
  "messages": messages,
211
150
  "system": system,
212
151
  }
213
- if response_format_info is not None:
214
- # TODO: This should not be here.. the model is resolved in the anthropic client.
215
- # This also means that chat is using the fast model...
216
- # I bet the same bug exists in gemini...
217
- provider_data["model"] = INLINE_COMPLETION_MODEL
218
152
  if tools:
219
153
  provider_data["tools"] = tools
220
154
  if response_format_info and response_format_info.name == "agent_response":
mito_ai/utils/create.py CHANGED
@@ -36,6 +36,17 @@ def is_user_json_exists_and_valid_json() -> bool:
36
36
  except:
37
37
  return False
38
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
39
50
 
40
51
  def try_create_user_json_file() -> None:
41
52
 
@@ -50,7 +61,12 @@ def try_create_user_json_file() -> None:
50
61
  with open(USER_JSON_PATH, 'w+') as f:
51
62
  f.write(json.dumps(USER_JSON_DEFAULT))
52
63
 
53
- # Then, we take special care to put all the testing/CI environments
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
54
70
  # (e.g. Github actions) under one ID and email
55
71
  if is_running_test():
56
72
  set_user_field(UJ_STATIC_USER_ID, GITHUB_ACTION_ID)
@@ -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