mito-ai 0.1.41__py3-none-any.whl → 0.1.43__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of mito-ai might be problematic. Click here for more details.

Files changed (56) hide show
  1. mito_ai/__init__.py +7 -0
  2. mito_ai/_version.py +1 -1
  3. mito_ai/app_manager/__init__.py +4 -0
  4. mito_ai/app_manager/handlers.py +134 -0
  5. mito_ai/app_manager/models.py +57 -0
  6. mito_ai/app_manager/utils.py +24 -0
  7. mito_ai/completions/completion_handlers/agent_execution_handler.py +1 -1
  8. mito_ai/completions/completion_handlers/chat_completion_handler.py +2 -2
  9. mito_ai/completions/completion_handlers/utils.py +99 -37
  10. mito_ai/completions/prompt_builders/utils.py +7 -1
  11. mito_ai/file_uploads/handlers.py +49 -26
  12. mito_ai/tests/completions/completion_handlers_utils_test.py +190 -0
  13. mito_ai/tests/file_uploads/test_handlers.py +15 -0
  14. {mito_ai-0.1.41.data → mito_ai-0.1.43.data}/data/share/jupyter/labextensions/mito_ai/build_log.json +100 -100
  15. {mito_ai-0.1.41.data → mito_ai-0.1.43.data}/data/share/jupyter/labextensions/mito_ai/package.json +2 -2
  16. {mito_ai-0.1.41.data → mito_ai-0.1.43.data}/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/package.json.orig +1 -1
  17. mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.01a962c68c8fae380f30.js → mito_ai-0.1.43.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.81703ac2bc645e5c2fc2.js +1324 -247
  18. mito_ai-0.1.43.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.81703ac2bc645e5c2fc2.js.map +1 -0
  19. mito_ai-0.1.43.data/data/share/jupyter/labextensions/mito_ai/static/node_modules_process_browser_js.4b128e94d31a81ebd209.js +198 -0
  20. mito_ai-0.1.43.data/data/share/jupyter/labextensions/mito_ai/static/node_modules_process_browser_js.4b128e94d31a81ebd209.js.map +1 -0
  21. mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.9a70f033717ba8689564.js → mito_ai-0.1.43.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.502aef26f0416fab7435.js +23 -23
  22. mito_ai-0.1.43.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.502aef26f0416fab7435.js.map +1 -0
  23. mito_ai-0.1.43.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
  24. mito_ai-0.1.43.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
  25. mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_tokenProvider_tokenProvider_mjs.16430abf3466c3153f59.js → mito_ai-0.1.43.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_tokenProvider_tokenProvider_-72f1c8.a917210f057fcfe224ad.js +2977 -610
  26. mito_ai-0.1.43.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
  27. mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_ui-react_dist_esm_index_mjs.61289bff0db44828605b.js → mito_ai-0.1.43.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_ui-react_dist_esm_index_mjs.4fcecd65bef9e9847609.js +1 -481
  28. mito_ai-0.1.43.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_ui-react_dist_esm_index_mjs.4fcecd65bef9e9847609.js.map +1 -0
  29. mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_core_dist_esm_singleton_apis_fetchAuthSession_mjs-node_modul-758875.dc495fd682071d97070c.js → mito_ai-0.1.43.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 +2 -60
  30. mito_ai-0.1.43.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
  31. mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.9795f79265ddb416864b.js → mito_ai-0.1.43.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.3f6754ac5116d47de76b.js +2 -240
  32. mito_ai-0.1.43.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.3f6754ac5116d47de76b.js.map +1 -0
  33. {mito_ai-0.1.41.dist-info → mito_ai-0.1.43.dist-info}/METADATA +1 -1
  34. {mito_ai-0.1.41.dist-info → mito_ai-0.1.43.dist-info}/RECORD +46 -41
  35. mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.01a962c68c8fae380f30.js.map +0 -1
  36. mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/node_modules_aws-amplify_core_dist_esm_singleton_apis_fetchAuthSession_mjs.182232e7bc6311fe4528.js +0 -63
  37. mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/node_modules_aws-amplify_core_dist_esm_singleton_apis_fetchAuthSession_mjs.182232e7bc6311fe4528.js.map +0 -1
  38. mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.9a70f033717ba8689564.js.map +0 -1
  39. mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_tokenProvider_tokenProvider_mjs.16430abf3466c3153f59.js.map +0 -1
  40. mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_core_dist_esm_singleton_Amplify_mjs.3c0035b95fe369aede82.js +0 -2345
  41. mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_core_dist_esm_singleton_Amplify_mjs.3c0035b95fe369aede82.js.map +0 -1
  42. mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_core_dist_esm_singleton_apis_fetchAuthSession_mjs-node_modul-758875.dc495fd682071d97070c.js.map +0 -1
  43. mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_ui-react_dist_esm_index_mjs.61289bff0db44828605b.js.map +0 -1
  44. mito_ai-0.1.41.data/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.9795f79265ddb416864b.js.map +0 -1
  45. {mito_ai-0.1.41.data → mito_ai-0.1.43.data}/data/etc/jupyter/jupyter_server_config.d/mito_ai.json +0 -0
  46. {mito_ai-0.1.41.data → mito_ai-0.1.43.data}/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/toolbar-buttons.json +0 -0
  47. {mito_ai-0.1.41.data → mito_ai-0.1.43.data}/data/share/jupyter/labextensions/mito_ai/static/style.js +0 -0
  48. {mito_ai-0.1.41.data → mito_ai-0.1.43.data}/data/share/jupyter/labextensions/mito_ai/static/style_index_js.5876024bb17dbd6a3ee6.js +0 -0
  49. {mito_ai-0.1.41.data → mito_ai-0.1.43.data}/data/share/jupyter/labextensions/mito_ai/static/style_index_js.5876024bb17dbd6a3ee6.js.map +0 -0
  50. {mito_ai-0.1.41.data → mito_ai-0.1.43.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_dist_esm_index_mjs.6bac1a8c4cc93f15f6b7.js +0 -0
  51. {mito_ai-0.1.41.data → mito_ai-0.1.43.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_dist_esm_index_mjs.6bac1a8c4cc93f15f6b7.js.map +0 -0
  52. {mito_ai-0.1.41.data → mito_ai-0.1.43.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_vscode-diff_dist_index_js.ea55f1f9346638aafbcf.js +0 -0
  53. {mito_ai-0.1.41.data → mito_ai-0.1.43.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_vscode-diff_dist_index_js.ea55f1f9346638aafbcf.js.map +0 -0
  54. {mito_ai-0.1.41.dist-info → mito_ai-0.1.43.dist-info}/WHEEL +0 -0
  55. {mito_ai-0.1.41.dist-info → mito_ai-0.1.43.dist-info}/entry_points.txt +0 -0
  56. {mito_ai-0.1.41.dist-info → mito_ai-0.1.43.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,190 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ import base64
5
+ import os
6
+ import tempfile
7
+ from contextlib import contextmanager
8
+ from mito_ai.completions.completion_handlers.utils import (
9
+ create_ai_optimized_message,
10
+ extract_and_encode_images_from_additional_context,
11
+ )
12
+
13
+
14
+ @contextmanager
15
+ def temporary_image_file(suffix=".png", content=b"fake_image_data"):
16
+ """Context manager that creates a temporary image file for testing."""
17
+ with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as temp_file:
18
+ temp_file.write(content)
19
+ temp_file_path = temp_file.name
20
+
21
+ try:
22
+ yield temp_file_path
23
+ finally:
24
+ # Clean up the temporary file
25
+ os.unlink(temp_file_path)
26
+
27
+
28
+ def test_text_only_message():
29
+ """Test scenario where the user only inputs text"""
30
+ result = create_ai_optimized_message("Hello world")
31
+
32
+ assert result["role"] == "user"
33
+ assert result["content"] == "Hello world"
34
+
35
+
36
+ def test_message_with_uploaded_image():
37
+ """Test scenario where the user uploads an image"""
38
+ with temporary_image_file() as temp_file_path:
39
+ result = create_ai_optimized_message(
40
+ text="Analyze this",
41
+ additional_context=[{"type": "image/png", "value": temp_file_path}],
42
+ )
43
+
44
+ assert result["role"] == "user"
45
+ assert isinstance(result["content"], list)
46
+ assert result["content"][0]["type"] == "text"
47
+ assert result["content"][1]["type"] == "image_url"
48
+
49
+
50
+ def test_message_with_multiple_uploaded_images():
51
+ """Test scenario where the user uploads multiple images"""
52
+ with temporary_image_file(suffix=".png", content=b"image1_data") as temp_file1:
53
+ with temporary_image_file(suffix=".jpg", content=b"image2_data") as temp_file2:
54
+ result = create_ai_optimized_message(
55
+ text="Analyze these images",
56
+ additional_context=[
57
+ {"type": "image/png", "value": temp_file1},
58
+ {"type": "image/jpeg", "value": temp_file2},
59
+ ],
60
+ )
61
+
62
+ assert result["role"] == "user"
63
+ assert isinstance(result["content"], list)
64
+ assert len(result["content"]) == 3 # text + 2 images
65
+ assert result["content"][0]["type"] == "text"
66
+ assert result["content"][0]["text"] == "Analyze these images"
67
+ assert result["content"][1]["type"] == "image_url"
68
+ assert result["content"][2]["type"] == "image_url"
69
+
70
+ # Verify the image URLs are properly formatted
71
+ assert result["content"][1]["image_url"]["url"].startswith("data:image/png;base64,")
72
+ assert result["content"][2]["image_url"]["url"].startswith("data:image/jpeg;base64,")
73
+
74
+
75
+ def test_message_with_active_cell_output():
76
+ """Test scenario where the active cell has an output"""
77
+ result = create_ai_optimized_message(
78
+ text="Analyze this", base64EncodedActiveCellOutput="cell_output_data"
79
+ )
80
+
81
+ assert result["role"] == "user"
82
+ assert isinstance(result["content"], list)
83
+ assert result["content"][0]["type"] == "text"
84
+ assert result["content"][1]["type"] == "image_url"
85
+
86
+
87
+ def test_message_with_uploaded_image_and_active_cell_output():
88
+ """Test scenario where the user uploads an image and the active cell has an output"""
89
+ with temporary_image_file() as temp_file_path:
90
+ result = create_ai_optimized_message(
91
+ text="Analyze this",
92
+ additional_context=[{"type": "image/png", "value": temp_file_path}],
93
+ base64EncodedActiveCellOutput="cell_output_data",
94
+ )
95
+
96
+ assert result["role"] == "user"
97
+ assert isinstance(result["content"], list)
98
+ assert result["content"][0]["type"] == "text"
99
+ assert result["content"][1]["type"] == "image_url"
100
+ assert result["content"][2]["type"] == "image_url"
101
+
102
+
103
+ def test_extract_and_encode_images_from_additional_context_valid_image():
104
+ """Test extracting and encoding a valid image file"""
105
+ with temporary_image_file() as temp_file_path:
106
+ additional_context = [{"type": "image/png", "value": temp_file_path}]
107
+
108
+ encoded_images = extract_and_encode_images_from_additional_context(
109
+ additional_context
110
+ )
111
+
112
+ assert len(encoded_images) == 1
113
+ assert encoded_images[0].startswith("data:image/png;base64,")
114
+ # Verify it's valid base64 by checking it can be decoded
115
+ base64_data = encoded_images[0].split(",")[1]
116
+ decoded_data = base64.b64decode(base64_data)
117
+ assert decoded_data == b"fake_image_data"
118
+
119
+
120
+ def test_extract_and_encode_images_from_additional_context_multiple_images():
121
+ """Test extracting and encoding multiple image files"""
122
+ with temporary_image_file(suffix=".png", content=b"image1_data") as temp_file1:
123
+ with temporary_image_file(suffix=".jpg", content=b"image2_data") as temp_file2:
124
+ additional_context = [
125
+ {"type": "image/png", "value": temp_file1},
126
+ {"type": "image/jpeg", "value": temp_file2},
127
+ ]
128
+
129
+ encoded_images = extract_and_encode_images_from_additional_context(
130
+ additional_context
131
+ )
132
+
133
+ assert len(encoded_images) == 2
134
+ assert encoded_images[0].startswith("data:image/png;base64,")
135
+ assert encoded_images[1].startswith("data:image/jpeg;base64,")
136
+
137
+
138
+ def test_extract_and_encode_images_from_additional_context_invalid_file():
139
+ """Test handling of invalid/non-existent image files"""
140
+ additional_context = [{"type": "image/png", "value": "non_existent_file.png"}]
141
+
142
+ encoded_images = extract_and_encode_images_from_additional_context(
143
+ additional_context
144
+ )
145
+
146
+ assert len(encoded_images) == 0
147
+
148
+
149
+ def test_extract_and_encode_images_from_additional_context_non_image_types():
150
+ """Test that non-image types are ignored"""
151
+ with temporary_image_file(suffix=".txt", content=b"text_data") as temp_file:
152
+ additional_context = [
153
+ {"type": "text/plain", "value": temp_file},
154
+ {"type": "application/pdf", "value": "document.pdf"},
155
+ ]
156
+
157
+ encoded_images = extract_and_encode_images_from_additional_context(
158
+ additional_context
159
+ )
160
+
161
+ assert len(encoded_images) == 0
162
+
163
+
164
+ def test_extract_and_encode_images_from_additional_context_mixed_types():
165
+ """Test handling of mixed image and non-image types"""
166
+ with temporary_image_file() as temp_image_file:
167
+ additional_context = [
168
+ {"type": "image/png", "value": temp_image_file},
169
+ {"type": "text/plain", "value": "document.txt"},
170
+ {"type": "image/jpeg", "value": "non_existent.jpg"},
171
+ ]
172
+
173
+ encoded_images = extract_and_encode_images_from_additional_context(
174
+ additional_context
175
+ )
176
+
177
+ # Should only have the valid PNG image
178
+ assert len(encoded_images) == 1
179
+ assert encoded_images[0].startswith("data:image/png;base64,")
180
+
181
+
182
+ def test_extract_and_encode_images_from_additional_context_empty():
183
+ """Test handling of empty or None additional_context"""
184
+ # Test with None
185
+ encoded_images = extract_and_encode_images_from_additional_context(None)
186
+ assert len(encoded_images) == 0
187
+
188
+ # Test with empty list
189
+ encoded_images = extract_and_encode_images_from_additional_context([])
190
+ assert len(encoded_images) == 0
@@ -265,3 +265,18 @@ def test_save_chunk(handler, temp_dir):
265
265
 
266
266
  # Clean up
267
267
  del handler._temp_dirs[filename]
268
+
269
+
270
+ def test_image_size_limit_exceeded(handler, temp_dir):
271
+ """Test that image uploads exceeding 3MB are rejected."""
272
+ filename = "large_image.jpg"
273
+ # Create 5MB of data (5 * 1024 * 1024 bytes)
274
+ file_data = b"x" * (5 * 1024 * 1024)
275
+ notebook_dir = temp_dir
276
+
277
+ # The _handle_regular_upload should raise a ValueError for oversized images
278
+ with pytest.raises(ValueError) as exc_info:
279
+ handler._handle_regular_upload(filename, file_data, notebook_dir)
280
+
281
+ # Verify the error message mentions the size limit
282
+ assert "exceeded 3MB limit" in str(exc_info.value)