mito-ai 0.1.43__py3-none-any.whl → 0.1.44__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.
- mito_ai/__init__.py +3 -3
- mito_ai/_version.py +1 -1
- mito_ai/{app_builder → app_deploy}/__init__.py +1 -1
- mito_ai/{app_builder → app_deploy}/handlers.py +39 -28
- mito_ai/{app_builder → app_deploy}/models.py +13 -13
- mito_ai/app_manager/handlers.py +33 -0
- mito_ai/app_manager/models.py +15 -1
- mito_ai/completions/handlers.py +13 -0
- mito_ai/completions/models.py +4 -1
- mito_ai/completions/prompt_builders/agent_system_message.py +6 -4
- mito_ai/completions/providers.py +5 -11
- mito_ai/streamlit_conversion/streamlit_agent_handler.py +1 -1
- mito_ai/streamlit_conversion/streamlit_utils.py +10 -0
- mito_ai/streamlit_preview/handlers.py +47 -71
- mito_ai/streamlit_preview/utils.py +41 -0
- mito_ai/tests/streamlit_preview/test_streamlit_preview_handler.py +88 -0
- mito_ai/tests/streamlit_preview/test_streamlit_preview_manager.py +4 -1
- mito_ai/utils/telemetry_utils.py +15 -5
- {mito_ai-0.1.43.data → mito_ai-0.1.44.data}/data/share/jupyter/labextensions/mito_ai/build_log.json +1 -1
- {mito_ai-0.1.43.data → mito_ai-0.1.44.data}/data/share/jupyter/labextensions/mito_ai/package.json +2 -2
- {mito_ai-0.1.43.data → mito_ai-0.1.44.data}/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/package.json.orig +1 -1
- {mito_ai-0.1.43.data → mito_ai-0.1.44.data}/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/toolbar-buttons.json +0 -5
- mito_ai-0.1.43.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.81703ac2bc645e5c2fc2.js → mito_ai-0.1.44.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.cf2e3ad2797fbb53826b.js +1202 -701
- mito_ai-0.1.44.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.cf2e3ad2797fbb53826b.js.map +1 -0
- mito_ai-0.1.43.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.502aef26f0416fab7435.js → mito_ai-0.1.44.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.5482493d1270f55b7283.js +5 -5
- mito_ai-0.1.43.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.502aef26f0416fab7435.js.map → mito_ai-0.1.44.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.5482493d1270f55b7283.js.map +1 -1
- {mito_ai-0.1.43.dist-info → mito_ai-0.1.44.dist-info}/METADATA +1 -1
- {mito_ai-0.1.43.dist-info → mito_ai-0.1.44.dist-info}/RECORD +51 -49
- mito_ai-0.1.43.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.81703ac2bc645e5c2fc2.js.map +0 -1
- {mito_ai-0.1.43.data → mito_ai-0.1.44.data}/data/etc/jupyter/jupyter_server_config.d/mito_ai.json +0 -0
- {mito_ai-0.1.43.data → mito_ai-0.1.44.data}/data/share/jupyter/labextensions/mito_ai/static/node_modules_process_browser_js.4b128e94d31a81ebd209.js +0 -0
- {mito_ai-0.1.43.data → mito_ai-0.1.44.data}/data/share/jupyter/labextensions/mito_ai/static/node_modules_process_browser_js.4b128e94d31a81ebd209.js.map +0 -0
- {mito_ai-0.1.43.data → mito_ai-0.1.44.data}/data/share/jupyter/labextensions/mito_ai/static/style.js +0 -0
- {mito_ai-0.1.43.data → mito_ai-0.1.44.data}/data/share/jupyter/labextensions/mito_ai/static/style_index_js.5876024bb17dbd6a3ee6.js +0 -0
- {mito_ai-0.1.43.data → mito_ai-0.1.44.data}/data/share/jupyter/labextensions/mito_ai/static/style_index_js.5876024bb17dbd6a3ee6.js.map +0 -0
- {mito_ai-0.1.43.data → mito_ai-0.1.44.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 +0 -0
- {mito_ai-0.1.43.data → mito_ai-0.1.44.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 +0 -0
- {mito_ai-0.1.43.data → mito_ai-0.1.44.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_tokenProvider_tokenProvider_-72f1c8.a917210f057fcfe224ad.js +0 -0
- {mito_ai-0.1.43.data → mito_ai-0.1.44.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_auth_dist_esm_providers_cognito_tokenProvider_tokenProvider_-72f1c8.a917210f057fcfe224ad.js.map +0 -0
- {mito_ai-0.1.43.data → mito_ai-0.1.44.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_dist_esm_index_mjs.6bac1a8c4cc93f15f6b7.js +0 -0
- {mito_ai-0.1.43.data → mito_ai-0.1.44.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_dist_esm_index_mjs.6bac1a8c4cc93f15f6b7.js.map +0 -0
- {mito_ai-0.1.43.data → mito_ai-0.1.44.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_ui-react_dist_esm_index_mjs.4fcecd65bef9e9847609.js +0 -0
- {mito_ai-0.1.43.data → mito_ai-0.1.44.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_ui-react_dist_esm_index_mjs.4fcecd65bef9e9847609.js.map +0 -0
- {mito_ai-0.1.43.data → mito_ai-0.1.44.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 +0 -0
- {mito_ai-0.1.43.data → mito_ai-0.1.44.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 +0 -0
- {mito_ai-0.1.43.data → mito_ai-0.1.44.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.3f6754ac5116d47de76b.js +0 -0
- {mito_ai-0.1.43.data → mito_ai-0.1.44.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.3f6754ac5116d47de76b.js.map +0 -0
- {mito_ai-0.1.43.data → mito_ai-0.1.44.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_vscode-diff_dist_index_js.ea55f1f9346638aafbcf.js +0 -0
- {mito_ai-0.1.43.data → mito_ai-0.1.44.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_vscode-diff_dist_index_js.ea55f1f9346638aafbcf.js.map +0 -0
- {mito_ai-0.1.43.dist-info → mito_ai-0.1.44.dist-info}/WHEEL +0 -0
- {mito_ai-0.1.43.dist-info → mito_ai-0.1.44.dist-info}/entry_points.txt +0 -0
- {mito_ai-0.1.43.dist-info → mito_ai-0.1.44.dist-info}/licenses/LICENSE +0 -0
mito_ai/__init__.py
CHANGED
|
@@ -5,7 +5,7 @@ from typing import List, Dict
|
|
|
5
5
|
from jupyter_server.utils import url_path_join
|
|
6
6
|
from mito_ai.completions.handlers import CompletionHandler
|
|
7
7
|
from mito_ai.completions.providers import OpenAIProvider
|
|
8
|
-
from mito_ai.
|
|
8
|
+
from mito_ai.app_deploy.handlers import AppDeployHandler
|
|
9
9
|
from mito_ai.streamlit_preview.handlers import StreamlitPreviewHandler
|
|
10
10
|
from mito_ai.log.urls import get_log_urls
|
|
11
11
|
from mito_ai.version_check import VersionCheckHandler
|
|
@@ -71,8 +71,8 @@ def _load_jupyter_server_extension(server_app) -> None: # type: ignore
|
|
|
71
71
|
{"llm": open_ai_provider},
|
|
72
72
|
),
|
|
73
73
|
(
|
|
74
|
-
url_path_join(base_url, "mito-ai", "app-
|
|
75
|
-
|
|
74
|
+
url_path_join(base_url, "mito-ai", "app-deploy"),
|
|
75
|
+
AppDeployHandler,
|
|
76
76
|
{}
|
|
77
77
|
),
|
|
78
78
|
(
|
mito_ai/_version.py
CHANGED
|
@@ -7,13 +7,14 @@ import logging
|
|
|
7
7
|
from typing import Any, Union, Optional
|
|
8
8
|
import zipfile
|
|
9
9
|
import tempfile
|
|
10
|
+
from mito_ai.streamlit_conversion.streamlit_utils import get_app_path
|
|
10
11
|
from mito_ai.utils.create import initialize_user
|
|
11
12
|
from mito_ai.utils.version_utils import is_pro
|
|
12
13
|
from mito_ai.utils.websocket_base import BaseWebSocketHandler
|
|
13
|
-
from mito_ai.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
from mito_ai.app_deploy.models import (
|
|
15
|
+
DeployAppReply,
|
|
16
|
+
AppDeployError,
|
|
17
|
+
DeployAppRequest,
|
|
17
18
|
ErrorMessage,
|
|
18
19
|
MessageType
|
|
19
20
|
)
|
|
@@ -23,8 +24,8 @@ from mito_ai.constants import ACTIVE_STREAMLIT_BASE_URL
|
|
|
23
24
|
import requests
|
|
24
25
|
|
|
25
26
|
|
|
26
|
-
class
|
|
27
|
-
"""Handler for app
|
|
27
|
+
class AppDeployHandler(BaseWebSocketHandler):
|
|
28
|
+
"""Handler for app deploy requests."""
|
|
28
29
|
|
|
29
30
|
def initialize(self) -> None:
|
|
30
31
|
"""Initialize the WebSocket handler."""
|
|
@@ -57,6 +58,7 @@ class AppBuilderHandler(BaseWebSocketHandler):
|
|
|
57
58
|
Args:
|
|
58
59
|
message: The message received on the WebSocket.
|
|
59
60
|
"""
|
|
61
|
+
|
|
60
62
|
start = time.time()
|
|
61
63
|
|
|
62
64
|
# Convert bytes to string if needed
|
|
@@ -73,13 +75,13 @@ class AppBuilderHandler(BaseWebSocketHandler):
|
|
|
73
75
|
parsed_message = self.parse_message(message)
|
|
74
76
|
message_type = parsed_message.get('type')
|
|
75
77
|
|
|
76
|
-
if message_type == MessageType.
|
|
78
|
+
if message_type == MessageType.DEPLOY_APP.value:
|
|
77
79
|
# Handle build app request
|
|
78
|
-
|
|
79
|
-
await self.
|
|
80
|
+
deploy_app_request = DeployAppRequest(**parsed_message)
|
|
81
|
+
await self._handle_deploy_app(deploy_app_request)
|
|
80
82
|
else:
|
|
81
83
|
self.log.error(f"Unknown message type: {message_type}")
|
|
82
|
-
error =
|
|
84
|
+
error = AppDeployError(
|
|
83
85
|
error_type="InvalidRequest",
|
|
84
86
|
title=f"Unknown message type: {message_type}"
|
|
85
87
|
)
|
|
@@ -87,11 +89,11 @@ class AppBuilderHandler(BaseWebSocketHandler):
|
|
|
87
89
|
|
|
88
90
|
except ValueError as e:
|
|
89
91
|
self.log.error("Invalid app builder request", exc_info=e)
|
|
90
|
-
error =
|
|
92
|
+
error = AppDeployError.from_exception(e)
|
|
91
93
|
self.reply(ErrorMessage(**error.__dict__))
|
|
92
94
|
except Exception as e:
|
|
93
95
|
self.log.error("Error handling app builder message", exc_info=e)
|
|
94
|
-
error =
|
|
96
|
+
error = AppDeployError.from_exception(
|
|
95
97
|
e,
|
|
96
98
|
hint="An error occurred while building the app. Please check the logs for details."
|
|
97
99
|
)
|
|
@@ -100,7 +102,7 @@ class AppBuilderHandler(BaseWebSocketHandler):
|
|
|
100
102
|
latency_ms = round((time.time() - start) * 1000)
|
|
101
103
|
self.log.info(f"App builder handler processed in {latency_ms} ms.")
|
|
102
104
|
|
|
103
|
-
async def
|
|
105
|
+
async def _handle_deploy_app(self, message: DeployAppRequest) -> None:
|
|
104
106
|
"""Handle a build app request.
|
|
105
107
|
|
|
106
108
|
Args:
|
|
@@ -109,17 +111,17 @@ class AppBuilderHandler(BaseWebSocketHandler):
|
|
|
109
111
|
message_id = message.message_id
|
|
110
112
|
notebook_path = message.notebook_path
|
|
111
113
|
jwt_token = message.jwt_token
|
|
112
|
-
|
|
114
|
+
|
|
113
115
|
if not message_id:
|
|
114
116
|
self.log.error("Missing message_id in request")
|
|
115
117
|
return
|
|
116
118
|
|
|
117
119
|
if not notebook_path:
|
|
118
|
-
error =
|
|
120
|
+
error = AppDeployError(
|
|
119
121
|
error_type="InvalidRequest",
|
|
120
122
|
title="Missing 'notebook_path' parameter"
|
|
121
123
|
)
|
|
122
|
-
self.reply(
|
|
124
|
+
self.reply(DeployAppReply(
|
|
123
125
|
parent_id=message_id,
|
|
124
126
|
url="",
|
|
125
127
|
error=error
|
|
@@ -132,12 +134,12 @@ class AppBuilderHandler(BaseWebSocketHandler):
|
|
|
132
134
|
is_valid = self._validate_jwt_token(jwt_token) if jwt_token else False
|
|
133
135
|
if not is_valid or not jwt_token:
|
|
134
136
|
self.log.error("JWT token validation failed")
|
|
135
|
-
error =
|
|
137
|
+
error = AppDeployError(
|
|
136
138
|
error_type="Unauthorized",
|
|
137
139
|
title="Invalid authentication token",
|
|
138
140
|
hint="Please sign in again to deploy your app."
|
|
139
141
|
)
|
|
140
|
-
self.reply(
|
|
142
|
+
self.reply(DeployAppReply(
|
|
141
143
|
parent_id=message_id,
|
|
142
144
|
url="",
|
|
143
145
|
error=error
|
|
@@ -150,25 +152,34 @@ class AppBuilderHandler(BaseWebSocketHandler):
|
|
|
150
152
|
notebook_path = str(notebook_path) if notebook_path else ""
|
|
151
153
|
|
|
152
154
|
app_directory = os.path.dirname(notebook_path)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
155
|
+
|
|
156
|
+
# Check if the app.py file exists
|
|
157
|
+
app_path = get_app_path(app_directory)
|
|
158
|
+
if app_path is None:
|
|
159
|
+
error = AppDeployError(
|
|
160
|
+
error_type="AppNotFound",
|
|
161
|
+
title="App not found",
|
|
162
|
+
hint="Please make sure the app.py file exists in the same directory as the notebook."
|
|
163
|
+
)
|
|
164
|
+
self.reply(DeployAppReply(
|
|
165
|
+
parent_id=message_id,
|
|
166
|
+
url="",
|
|
167
|
+
error=error
|
|
168
|
+
))
|
|
169
|
+
|
|
170
|
+
# Finally, deploy the app
|
|
160
171
|
deploy_url = await self._deploy_app(app_directory, jwt_token)
|
|
161
172
|
|
|
162
173
|
# Send the response
|
|
163
|
-
self.reply(
|
|
174
|
+
self.reply(DeployAppReply(
|
|
164
175
|
parent_id=message_id,
|
|
165
176
|
url=deploy_url
|
|
166
177
|
))
|
|
167
178
|
|
|
168
179
|
except Exception as e:
|
|
169
180
|
self.log.error(f"Error building app: {e}", exc_info=e)
|
|
170
|
-
error =
|
|
171
|
-
self.reply(
|
|
181
|
+
error = AppDeployError.from_exception(e)
|
|
182
|
+
self.reply(DeployAppReply(
|
|
172
183
|
parent_id=message_id,
|
|
173
184
|
url="",
|
|
174
185
|
error=error
|
|
@@ -7,13 +7,13 @@ from typing import Literal, Optional
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class MessageType(str, Enum):
|
|
10
|
-
"""Types of app
|
|
11
|
-
|
|
10
|
+
"""Types of app deploy messages."""
|
|
11
|
+
DEPLOY_APP = "deploy-app"
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
@dataclass(frozen=True)
|
|
15
|
-
class
|
|
16
|
-
"""Error information for app
|
|
15
|
+
class AppDeployError:
|
|
16
|
+
"""Error information for app deploy operations."""
|
|
17
17
|
|
|
18
18
|
# Error type.
|
|
19
19
|
error_type: str
|
|
@@ -28,7 +28,7 @@ class AppBuilderError:
|
|
|
28
28
|
hint: Optional[str] = None
|
|
29
29
|
|
|
30
30
|
@classmethod
|
|
31
|
-
def from_exception(cls, e: Exception, hint: Optional[str] = None) -> "
|
|
31
|
+
def from_exception(cls, e: Exception, hint: Optional[str] = None) -> "AppDeployError":
|
|
32
32
|
"""Create an error from an exception.
|
|
33
33
|
|
|
34
34
|
Args:
|
|
@@ -47,7 +47,7 @@ class AppBuilderError:
|
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
@dataclass(frozen=True)
|
|
50
|
-
class ErrorMessage(
|
|
50
|
+
class ErrorMessage(AppDeployError):
|
|
51
51
|
"""Error message."""
|
|
52
52
|
|
|
53
53
|
# Message type.
|
|
@@ -55,11 +55,11 @@ class ErrorMessage(AppBuilderError):
|
|
|
55
55
|
|
|
56
56
|
|
|
57
57
|
@dataclass(frozen=True)
|
|
58
|
-
class
|
|
59
|
-
"""Request to
|
|
58
|
+
class DeployAppRequest:
|
|
59
|
+
"""Request to deploy an app."""
|
|
60
60
|
|
|
61
61
|
# Request type.
|
|
62
|
-
type: Literal["
|
|
62
|
+
type: Literal["deploy-app"]
|
|
63
63
|
|
|
64
64
|
# Message ID.
|
|
65
65
|
message_id: str
|
|
@@ -72,8 +72,8 @@ class BuildAppRequest:
|
|
|
72
72
|
|
|
73
73
|
|
|
74
74
|
@dataclass(frozen=True)
|
|
75
|
-
class
|
|
76
|
-
"""Reply to a
|
|
75
|
+
class DeployAppReply:
|
|
76
|
+
"""Reply to a deplpy app request."""
|
|
77
77
|
|
|
78
78
|
# ID of the request message this is replying to.
|
|
79
79
|
parent_id: str
|
|
@@ -82,7 +82,7 @@ class BuildAppReply:
|
|
|
82
82
|
url: str
|
|
83
83
|
|
|
84
84
|
# Optional error information.
|
|
85
|
-
error: Optional[
|
|
85
|
+
error: Optional[AppDeployError] = None
|
|
86
86
|
|
|
87
87
|
# Type of reply.
|
|
88
|
-
type: Literal["
|
|
88
|
+
type: Literal["deploy-app"] = "deploy-app"
|
mito_ai/app_manager/handlers.py
CHANGED
|
@@ -12,6 +12,8 @@ from mito_ai.app_manager.models import (
|
|
|
12
12
|
AppManagerError,
|
|
13
13
|
ManageAppRequest,
|
|
14
14
|
ManageAppReply,
|
|
15
|
+
CheckAppStatusRequest,
|
|
16
|
+
CheckAppStatusReply,
|
|
15
17
|
ErrorMessage,
|
|
16
18
|
MessageType
|
|
17
19
|
)
|
|
@@ -57,6 +59,10 @@ class AppManagerHandler(BaseWebSocketHandler):
|
|
|
57
59
|
# Handle manage app request
|
|
58
60
|
manage_app_request = ManageAppRequest(**parsed_message)
|
|
59
61
|
await self._handle_manage_app(manage_app_request)
|
|
62
|
+
elif message_type == MessageType.CHECK_APP_STATUS.value:
|
|
63
|
+
# Handle check app status request
|
|
64
|
+
check_status_request = CheckAppStatusRequest(**parsed_message)
|
|
65
|
+
await self._handle_check_app_status(check_status_request)
|
|
60
66
|
else:
|
|
61
67
|
self.log.error(f"Unknown message type: {message_type}")
|
|
62
68
|
error_response = ErrorMessage(
|
|
@@ -131,4 +137,31 @@ class AppManagerHandler(BaseWebSocketHandler):
|
|
|
131
137
|
error=error,
|
|
132
138
|
message_id=request.message_id
|
|
133
139
|
)
|
|
140
|
+
self.reply(error_reply)
|
|
141
|
+
|
|
142
|
+
async def _handle_check_app_status(self, request: CheckAppStatusRequest) -> None:
|
|
143
|
+
"""Handle a check app status request."""
|
|
144
|
+
self.log.info("In check app status")
|
|
145
|
+
try:
|
|
146
|
+
# Make a HEAD request to check if the app URL is accessible
|
|
147
|
+
response = requests.head(request.app_url, timeout=10, verify=False)
|
|
148
|
+
self.log.debug(f"Is app accessible: {response.status_code}")
|
|
149
|
+
is_accessible = response.status_code==200
|
|
150
|
+
|
|
151
|
+
# Create successful response
|
|
152
|
+
reply = CheckAppStatusReply(
|
|
153
|
+
is_accessible=is_accessible
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
self.reply(reply)
|
|
157
|
+
|
|
158
|
+
except Exception as e:
|
|
159
|
+
self.log.error(f"Error checking app status: {e}", exc_info=e)
|
|
160
|
+
error = AppManagerError.from_exception(e)
|
|
161
|
+
|
|
162
|
+
# Return error response
|
|
163
|
+
error_reply = CheckAppStatusReply(
|
|
164
|
+
is_accessible=False,
|
|
165
|
+
error=error
|
|
166
|
+
)
|
|
134
167
|
self.reply(error_reply)
|
mito_ai/app_manager/models.py
CHANGED
|
@@ -8,6 +8,7 @@ from typing import List, Optional
|
|
|
8
8
|
class MessageType(str, Enum):
|
|
9
9
|
"""Types of app manager messages."""
|
|
10
10
|
MANAGE_APP = "manage-app"
|
|
11
|
+
CHECK_APP_STATUS = "check-app-status"
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
@dataclass(frozen=True)
|
|
@@ -15,7 +16,7 @@ class ManageAppRequest:
|
|
|
15
16
|
"""Request to manage apps."""
|
|
16
17
|
type: str = "manage-app"
|
|
17
18
|
jwt_token: Optional[str] = None
|
|
18
|
-
message_id: Optional[str] = None
|
|
19
|
+
message_id: Optional[str] = None
|
|
19
20
|
|
|
20
21
|
@dataclass(frozen=True)
|
|
21
22
|
class App:
|
|
@@ -48,6 +49,19 @@ class ManageAppReply:
|
|
|
48
49
|
error: Optional[AppManagerError] = None
|
|
49
50
|
message_id: Optional[str] = None
|
|
50
51
|
|
|
52
|
+
@dataclass(frozen=True)
|
|
53
|
+
class CheckAppStatusRequest:
|
|
54
|
+
"""Request to check app status."""
|
|
55
|
+
app_url: str
|
|
56
|
+
type: str = "check-app-status"
|
|
57
|
+
|
|
58
|
+
@dataclass(frozen=True)
|
|
59
|
+
class CheckAppStatusReply:
|
|
60
|
+
"""Reply to a check app status request."""
|
|
61
|
+
is_accessible: bool
|
|
62
|
+
type: str = "check-app-status"
|
|
63
|
+
error: Optional[AppManagerError] = None
|
|
64
|
+
|
|
51
65
|
@dataclass(frozen=True)
|
|
52
66
|
class ErrorMessage:
|
|
53
67
|
"""Error message."""
|
mito_ai/completions/handlers.py
CHANGED
|
@@ -67,6 +67,7 @@ class CompletionHandler(JupyterHandler, WebSocketHandler):
|
|
|
67
67
|
self._llm = llm
|
|
68
68
|
self.is_pro = is_pro()
|
|
69
69
|
self._selected_model = FALLBACK_MODEL
|
|
70
|
+
self.is_electron = False
|
|
70
71
|
identify(llm.key_type)
|
|
71
72
|
|
|
72
73
|
@property
|
|
@@ -128,6 +129,18 @@ class CompletionHandler(JupyterHandler, WebSocketHandler):
|
|
|
128
129
|
parsed_message = json.loads(message)
|
|
129
130
|
metadata_dict = parsed_message.get('metadata', {})
|
|
130
131
|
type: MessageType = MessageType(parsed_message.get('type'))
|
|
132
|
+
|
|
133
|
+
# Extract environment information from the message
|
|
134
|
+
environment = parsed_message.get('environment', {})
|
|
135
|
+
if environment:
|
|
136
|
+
is_electron = environment.get('isElectron', None)
|
|
137
|
+
if is_electron is not None:
|
|
138
|
+
if is_electron != self.is_electron:
|
|
139
|
+
# If the is_electron status is different, log it
|
|
140
|
+
identify(key_type=self._llm.key_type, is_electron=is_electron)
|
|
141
|
+
|
|
142
|
+
self.is_electron = is_electron
|
|
143
|
+
|
|
131
144
|
except ValueError as e:
|
|
132
145
|
self.log.error("Invalid completion request.", exc_info=e)
|
|
133
146
|
return
|
mito_ai/completions/models.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import traceback
|
|
5
5
|
from dataclasses import dataclass, field
|
|
6
|
-
from typing import List, Literal, Optional, NewType, Dict
|
|
6
|
+
from typing import List, Literal, Optional, NewType, Dict, Any
|
|
7
7
|
from openai.types.chat import ChatCompletionMessageParam
|
|
8
8
|
from enum import Enum
|
|
9
9
|
from pydantic import BaseModel
|
|
@@ -155,6 +155,9 @@ class CompletionRequest:
|
|
|
155
155
|
# Whether to stream the response (if supported by the model).
|
|
156
156
|
stream: bool = False
|
|
157
157
|
|
|
158
|
+
# Environment information from the client
|
|
159
|
+
environment: Optional[Dict[str, Any]] = None
|
|
160
|
+
|
|
158
161
|
|
|
159
162
|
@dataclass(frozen=True)
|
|
160
163
|
class AICapabilities:
|
|
@@ -51,7 +51,8 @@ Format:
|
|
|
51
51
|
code: str
|
|
52
52
|
code_summary: str
|
|
53
53
|
cell_type: 'code' | 'markdown'
|
|
54
|
-
}}
|
|
54
|
+
}},
|
|
55
|
+
analysis_assumptions: Optional[List[str]]
|
|
55
56
|
}}
|
|
56
57
|
|
|
57
58
|
Important information:
|
|
@@ -60,7 +61,7 @@ Important information:
|
|
|
60
61
|
3. The code should be the full contents of that updated code cell. The code that you return will overwrite the existing contents of the code cell so it must contain all necessary code.
|
|
61
62
|
4. The code_summary must be a very short phrase (1–5 words maximum) that begins with a verb ending in "-ing" (e.g., "Loading data", "Filtering rows", "Calculating average", "Plotting revenue"). Avoid full sentences or explanations—this should read like a quick commit message or code label, not a description.
|
|
62
63
|
5. Important: Only use the CELL_UPDATE tool if you want to add/modify a notebook cell in response to the user's request. If the user is just sending you a friendly greeting or asking you a question about yourself, you SHOULD NOT USE A CELL_UPDATE tool because it does not require modifying the notebook. Instead, just use the FINISHED_TASK response.
|
|
63
|
-
6. The
|
|
64
|
+
6. The analysis_assumptions is an optional list of critical assumptions that you made about the data or analysis approach. The assumptions you list here will be displayed to the user so that they can confirm or correct the assumptions. For example: ["NaN values in the impressions column represent 0 impressions", "Only crashes with pedestrian or cyclist fatalities are considered fatal crashes", "Intervention priority combines both volume and severity to identify maximum impact opportunities"].
|
|
64
65
|
7. Only include important data and analytical assumptions that if incorrect would fundamentally change your analysis conclusions. These should be data handling decisions, methodological choices, and definitional boundaries. Do not include: obvious statements ("Each record is counted once"), result interpretation guidance ("Gaps in the plot represent zero values"), display choices ("Data is sorted for clarity"), internal reasoning ("Bar chart is better than line plot"), or environment assumptions ("Library X is installed"). Prioritize quality over quantity - include only the most critical assumptions or omit the field entirely if there are no critical assumptions made in this step that have not already be shared with the user. If you ever doubt whether an assumption is critical enough to be shared with the user as an assumption, don't include it. Most messages should not include an assumption.
|
|
65
66
|
8. Do not include the same assumption or variations of the same assumption multiple times in the same conversation. Once you have presented the assumption to the user, they will already have the opportunity to confirm or correct it so do not include it again.
|
|
66
67
|
|
|
@@ -77,7 +78,8 @@ Format:
|
|
|
77
78
|
code: str
|
|
78
79
|
code_summary: str
|
|
79
80
|
cell_type: 'code' | 'markdown'
|
|
80
|
-
}}
|
|
81
|
+
}},
|
|
82
|
+
analysis_assumptions: Optional[List[str]]
|
|
81
83
|
}}
|
|
82
84
|
|
|
83
85
|
Important information:
|
|
@@ -86,7 +88,7 @@ Important information:
|
|
|
86
88
|
3. The code should be the full contents of that updated code cell. The code that you return will overwrite the existing contents of the code cell so it must contain all necessary code.
|
|
87
89
|
4. code_summary must be a very short phrase (1–5 words maximum) that begins with a verb ending in "-ing" (e.g., "Loading data", "Filtering rows", "Calculating average", "Plotting revenue"). Avoid full sentences or explanations—this should read like a quick commit message or code label, not a description.
|
|
88
90
|
5. The cell_type should only be 'markdown' if there is no code to add. There may be times where the code has comments. These are still code cells and should have the cell_type 'code'. Any cells that are labeled 'markdown' will be converted to markdown cells by the user.
|
|
89
|
-
6. The
|
|
91
|
+
6. The analysis_assumptions is an optional list of critical assumptions that you made about the data or analysis approach. The assumptions you list here will be displayed to the user so that they can confirm or correct the assumptions. For example: ["NaN values in the impressions column represent 0 impressions", "Only crashes with pedestrian or cyclist fatalities are considered fatal crashes", "Intervention priority combines both volume and severity to identify maximum impact opportunities"].
|
|
90
92
|
7. Only include important data and analytical assumptions that if incorrect would fundamentally change your analysis conclusions. These should be data handling decisions, methodological choices, and definitional boundaries. Do not include: obvious statements ("Each record is counted once"), result interpretation guidance ("Gaps in the plot represent zero values"), display choices ("Data is sorted for clarity"), internal reasoning ("Bar chart is better than line plot"), or environment assumptions ("Library X is installed"). Prioritize quality over quantity - include only the most critical assumptions or omit the field entirely if there are no critical assumptions made in this step that have not already be shared with the user. If you ever doubt whether an assumption is critical enough to be shared with the user as an assumption, don't include it. Most messages should not include an assumption.
|
|
91
93
|
8. Do not include the same assumption or variations of the same assumption multiple times in the same conversation. Once you have presented the assumption to the user, they will already have the opportunity to confirm or correct it so do not include it again.
|
|
92
94
|
|
mito_ai/completions/providers.py
CHANGED
|
@@ -160,7 +160,7 @@ This attribute is observed by the websocket provider to push the error to the cl
|
|
|
160
160
|
# If we hit a free tier limit, then raise an exception right away without retrying.
|
|
161
161
|
self.log.exception(f"Error during request_completions: {e}")
|
|
162
162
|
self.last_error = CompletionError.from_exception(e)
|
|
163
|
-
log_ai_completion_error('user_key' if self.key_type != MITO_SERVER_KEY else 'mito_server_key', message_type, e)
|
|
163
|
+
log_ai_completion_error('user_key' if self.key_type != MITO_SERVER_KEY else 'mito_server_key', thread_id or "", message_type, e)
|
|
164
164
|
raise
|
|
165
165
|
|
|
166
166
|
except BaseException as e:
|
|
@@ -169,14 +169,14 @@ This attribute is observed by the websocket provider to push the error to the cl
|
|
|
169
169
|
# Exponential backoff: wait 2^attempt seconds
|
|
170
170
|
wait_time = 2 ** attempt
|
|
171
171
|
self.log.info(f"Retrying request_completions after {wait_time}s (attempt {attempt + 1}/{max_retries + 1}): {str(e)}")
|
|
172
|
-
log_ai_completion_retry('user_key' if self.key_type != MITO_SERVER_KEY else 'mito_server_key', message_type, e)
|
|
172
|
+
log_ai_completion_retry('user_key' if self.key_type != MITO_SERVER_KEY else 'mito_server_key', thread_id or "", message_type, e)
|
|
173
173
|
await asyncio.sleep(wait_time)
|
|
174
174
|
continue
|
|
175
175
|
else:
|
|
176
176
|
# Final failure after all retries - set error state and raise
|
|
177
177
|
self.log.exception(f"Error during request_completions after {attempt + 1} attempts: {e}")
|
|
178
178
|
self.last_error = CompletionError.from_exception(e)
|
|
179
|
-
log_ai_completion_error('user_key' if self.key_type != MITO_SERVER_KEY else 'mito_server_key', message_type, e)
|
|
179
|
+
log_ai_completion_error('user_key' if self.key_type != MITO_SERVER_KEY else 'mito_server_key', thread_id or "", message_type, e)
|
|
180
180
|
raise
|
|
181
181
|
|
|
182
182
|
# This should never be reached due to the raise in the except block,
|
|
@@ -264,14 +264,8 @@ This attribute is observed by the websocket provider to push the error to the cl
|
|
|
264
264
|
except BaseException as e:
|
|
265
265
|
self.log.exception(f"Error during stream_completions: {e}")
|
|
266
266
|
self.last_error = CompletionError.from_exception(e)
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
params={
|
|
270
|
-
KEY_TYPE_PARAM: self.key_type,
|
|
271
|
-
'message_type': message_type.value,
|
|
272
|
-
},
|
|
273
|
-
error=e
|
|
274
|
-
)
|
|
267
|
+
log_ai_completion_error('user_key' if self.key_type != MITO_SERVER_KEY else 'mito_server_key', thread_id, message_type, e)
|
|
268
|
+
|
|
275
269
|
# Send error message to client before raising
|
|
276
270
|
reply_fn(CompletionStreamChunk(
|
|
277
271
|
parent_id=message_id,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import logging
|
|
5
5
|
import os
|
|
6
6
|
from anthropic.types import MessageParam
|
|
7
|
-
from typing import List, Optional, Tuple, cast
|
|
7
|
+
from typing import List, Optional, Tuple, cast
|
|
8
8
|
|
|
9
9
|
from mito_ai.logger import get_logger
|
|
10
10
|
from mito_ai.streamlit_conversion.agent_utils import apply_patch_to_text, extract_todo_placeholders, fix_diff_headers
|
|
@@ -60,7 +60,17 @@ def create_app_file(app_directory: str, code: str) -> Tuple[bool, str, str]:
|
|
|
60
60
|
return False, '', f"Error creating file: {str(e)}"
|
|
61
61
|
except Exception as e:
|
|
62
62
|
return False, '', f"Unexpected error: {str(e)}"
|
|
63
|
+
|
|
63
64
|
|
|
65
|
+
def get_app_path(app_directory: str) -> Optional[str]:
|
|
66
|
+
"""
|
|
67
|
+
Check if the app.py file exists in the given directory.
|
|
68
|
+
"""
|
|
69
|
+
app_path = os.path.join(app_directory, "app.py")
|
|
70
|
+
if not os.path.exists(app_path):
|
|
71
|
+
return None
|
|
72
|
+
return app_path
|
|
73
|
+
|
|
64
74
|
|
|
65
75
|
def parse_jupyter_notebook_to_extract_required_content(notebook_path: str) -> Dict[str, Any]:
|
|
66
76
|
"""
|