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
mito_ai/__init__.py CHANGED
@@ -5,12 +5,33 @@ 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.app_builder.handlers import AppBuilderHandler
8
+ from mito_ai.completions.message_history import GlobalMessageHistory
9
+ from mito_ai.app_deploy.handlers import AppDeployHandler
10
+ from mito_ai.streamlit_preview.handlers import StreamlitPreviewHandler
9
11
  from mito_ai.log.urls import get_log_urls
10
12
  from mito_ai.version_check import VersionCheckHandler
11
13
  from mito_ai.db.urls import get_db_urls
12
14
  from mito_ai.settings.urls import get_settings_urls
13
15
  from mito_ai.rules.urls import get_rules_urls
16
+ from mito_ai.auth.urls import get_auth_urls
17
+ from mito_ai.streamlit_preview.urls import get_streamlit_preview_urls
18
+ from mito_ai.app_manager.handlers import AppManagerHandler
19
+ from mito_ai.file_uploads.urls import get_file_uploads_urls
20
+ from mito_ai.user.urls import get_user_urls
21
+ from mito_ai.chat_history.urls import get_chat_history_urls
22
+
23
+ # Force Matplotlib to use the Jupyter inline backend.
24
+ # Background: importing Streamlit sets os.environ["MPLBACKEND"] = "Agg" very early.
25
+ # In a Jupyter kernel, that selects a non‑interactive canvas and can trigger:
26
+ # "UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown"
27
+ # which prevents figures from rendering in notebook outputs.
28
+ # We preempt this by selecting the canonical Jupyter inline backend BEFORE any
29
+ # Matplotlib import, so figures render inline reliably. This must run very early.
30
+ # See: https://github.com/streamlit/streamlit/issues/9640
31
+
32
+ import os
33
+ os.environ["MPLBACKEND"] = "module://matplotlib_inline.backend_inline"
34
+
14
35
  try:
15
36
  from _version import __version__
16
37
  except ImportError:
@@ -44,31 +65,50 @@ def _load_jupyter_server_extension(server_app) -> None: # type: ignore
44
65
  base_url = web_app.settings["base_url"]
45
66
 
46
67
  open_ai_provider = OpenAIProvider(config=server_app.config)
68
+
69
+ # Create a single GlobalMessageHistory instance for the entire server
70
+ # This ensures thread-safe access to the .mito/ai-chats directory
71
+ global_message_history = GlobalMessageHistory()
47
72
 
48
73
  # WebSocket handlers
49
74
  handlers = [
50
75
  (
51
76
  url_path_join(base_url, "mito-ai", "completions"),
52
77
  CompletionHandler,
53
- {"llm": open_ai_provider},
78
+ {"llm": open_ai_provider, "message_history": global_message_history},
79
+ ),
80
+ (
81
+ url_path_join(base_url, "mito-ai", "app-deploy"),
82
+ AppDeployHandler,
83
+ {}
54
84
  ),
55
85
  (
56
- url_path_join(base_url, "mito-ai", "app-builder"),
57
- AppBuilderHandler,
86
+ url_path_join(base_url, "mito-ai", "streamlit-preview"),
87
+ StreamlitPreviewHandler,
58
88
  {}
59
89
  ),
60
90
  (
61
91
  url_path_join(base_url, "mito-ai", "version-check"),
62
92
  VersionCheckHandler,
63
93
  {},
94
+ ),
95
+ (
96
+ url_path_join(base_url, "mito-ai", "app-manager"),
97
+ AppManagerHandler,
98
+ {}
64
99
  )
65
100
  ]
66
101
 
67
102
  # REST API endpoints
68
- handlers.extend(get_db_urls(base_url))
69
- handlers.extend(get_settings_urls(base_url))
70
- handlers.extend(get_rules_urls(base_url))
71
- handlers.extend(get_log_urls(base_url))
72
-
103
+ handlers.extend(get_db_urls(base_url)) # type: ignore
104
+ handlers.extend(get_settings_urls(base_url)) # type: ignore
105
+ handlers.extend(get_rules_urls(base_url)) # type: ignore
106
+ handlers.extend(get_log_urls(base_url, open_ai_provider.key_type)) # type: ignore
107
+ handlers.extend(get_auth_urls(base_url)) # type: ignore
108
+ handlers.extend(get_streamlit_preview_urls(base_url)) # type: ignore
109
+ handlers.extend(get_file_uploads_urls(base_url)) # type: ignore
110
+ handlers.extend(get_user_urls(base_url)) # type: ignore
111
+ handlers.extend(get_chat_history_urls(base_url, global_message_history)) # type: ignore
112
+
73
113
  web_app.add_handlers(host_pattern, handlers)
74
114
  server_app.log.info("Loaded the mito_ai server extension")
mito_ai/_version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # This file is auto-generated by Hatchling. As such, do not:
2
2
  # - modify
3
3
  # - track in version control e.g. be sure to add to .gitignore
4
- __version__ = VERSION = '0.1.33'
4
+ __version__ = VERSION = '0.1.49'
@@ -5,8 +5,9 @@ import json
5
5
  import anthropic
6
6
  from typing import Dict, Any, Optional, Tuple, Union, Callable, List, cast
7
7
 
8
- from anthropic.types import Message, MessageParam
8
+ from anthropic.types import Message, MessageParam, TextBlockParam
9
9
  from mito_ai.completions.models import ResponseFormatInfo, CompletionReply, CompletionStreamChunk, CompletionItem, MessageType
10
+ from mito_ai.constants import MESSAGE_HISTORY_TRIM_THRESHOLD
10
11
  from openai.types.chat import ChatCompletionMessageParam
11
12
  from mito_ai.utils.anthropic_utils import get_anthropic_completion_from_mito_server, stream_anthropic_completion_from_mito_server, get_anthropic_completion_function_params
12
13
 
@@ -15,8 +16,6 @@ from mito_ai.utils.anthropic_utils import get_anthropic_completion_from_mito_ser
15
16
  # 8192 is the maximum allowed number of output tokens for claude-3-5-haiku-20241022
16
17
  MAX_TOKENS = 8_000
17
18
 
18
- ANTHROPIC_FAST_MODEL = "claude-3-5-haiku-latest"
19
-
20
19
  def extract_and_parse_anthropic_json_response(response: Message) -> Union[object, Any]:
21
20
  """
22
21
  Extracts and parses the JSON response from the Claude API.
@@ -53,12 +52,12 @@ def extract_and_parse_anthropic_json_response(response: Message) -> Union[object
53
52
 
54
53
 
55
54
  def get_anthropic_system_prompt_and_messages(messages: List[ChatCompletionMessageParam]) -> Tuple[
56
- Union[str, anthropic.NotGiven], List[MessageParam]]:
55
+ Union[str, anthropic.Omit], List[MessageParam]]:
57
56
  """
58
57
  Convert a list of OpenAI messages to a list of Anthropic messages.
59
58
  """
60
59
 
61
- system_prompt: Union[str, anthropic.NotGiven] = anthropic.NotGiven()
60
+ system_prompt: Union[str, anthropic.Omit] = anthropic.Omit()
62
61
  anthropic_messages: List[MessageParam] = []
63
62
 
64
63
  for message in messages:
@@ -126,14 +125,97 @@ def get_anthropic_system_prompt_and_messages(messages: List[ChatCompletionMessag
126
125
  return system_prompt, anthropic_messages
127
126
 
128
127
 
128
+ def add_cache_control_to_message(message: MessageParam) -> MessageParam:
129
+ """
130
+ Adds cache_control to a message's content.
131
+ Handles both string content and list of content blocks.
132
+ """
133
+ content = message.get("content")
134
+
135
+ if isinstance(content, str):
136
+ # Simple string content - convert to list format with cache_control
137
+ return {
138
+ "role": message["role"],
139
+ "content": [
140
+ {
141
+ "type": "text",
142
+ "text": content,
143
+ "cache_control": {"type": "ephemeral"}
144
+ }
145
+ ]
146
+ }
147
+
148
+ elif isinstance(content, list) and len(content) > 0:
149
+ # List of content blocks - add cache_control to last block
150
+ content_blocks = content.copy()
151
+ last_block = content_blocks[-1].copy()
152
+ last_block["cache_control"] = {"type": "ephemeral"}
153
+ content_blocks[-1] = last_block
154
+
155
+ return {
156
+ "role": message["role"],
157
+ "content": content_blocks
158
+ }
159
+
160
+ else:
161
+ # Edge case: empty or malformed content
162
+ return message
163
+
164
+
165
+ def get_anthropic_system_prompt_and_messages_with_caching(messages: List[ChatCompletionMessageParam]) -> Tuple[
166
+ Union[str, List[TextBlockParam], anthropic.Omit], List[MessageParam]]:
167
+ """
168
+ Convert a list of OpenAI messages to a list of Anthropic messages with caching applied.
169
+
170
+ Caching Strategy:
171
+ 1. System prompt (static) → Always cached
172
+ 2. Stable conversation history → Cache at keep_recent boundary
173
+ 3. Recent messages → Never cached (always fresh)
174
+
175
+ The keep_recent parameter determines which messages are stable and won't be trimmed.
176
+ We cache at the keep_recent boundary because those messages are guaranteed to be stable.
177
+ """
178
+
179
+ # Get the base system prompt and messages
180
+ system_prompt, anthropic_messages = get_anthropic_system_prompt_and_messages(messages)
181
+
182
+ # 1. Cache the system prompt always
183
+ # If the system prompt is something like anthropic.Omit, we don't need to cache it
184
+ cached_system_prompt: Union[str, List[TextBlockParam], anthropic.Omit] = system_prompt
185
+ if isinstance(system_prompt, str):
186
+ cached_system_prompt = [{
187
+ "type": "text",
188
+ "text": system_prompt,
189
+ "cache_control": {"type": "ephemeral"}
190
+ }]
191
+
192
+ # 2. Cache conversation history at the boundary where the messages are stable.
193
+ # Messages are stable after they are more than MESSAGE_HISTORY_TRIM_THRESHOLD old.
194
+ # At this point, the messages are not edited anymore, so they will not invalidate the cache.
195
+ # If we included the messages before the boundary in the cache, then every time we send a new
196
+ # message, we would invalidate the cache and we would never get a cache hit except for the system prompt.
197
+ messages_with_cache = []
198
+
199
+ if len(anthropic_messages) > 0:
200
+ cache_boundary = len(anthropic_messages) - MESSAGE_HISTORY_TRIM_THRESHOLD - 1
201
+
202
+ # Add all messages, but only add cache_control to the message at the boundary
203
+ for i, msg in enumerate(anthropic_messages):
204
+ if i == cache_boundary:
205
+ messages_with_cache.append(add_cache_control_to_message(msg))
206
+ else:
207
+ messages_with_cache.append(msg)
208
+
209
+ return cached_system_prompt, messages_with_cache
210
+
211
+
129
212
  class AnthropicClient:
130
213
  """
131
214
  A client for interacting with the Anthropic API or the Mito server fallback.
132
215
  """
133
216
 
134
- def __init__(self, api_key: Optional[str], model: str, timeout: int = 30, max_retries: int = 1):
217
+ def __init__(self, api_key: Optional[str], timeout: int = 30, max_retries: int = 1):
135
218
  self.api_key = api_key
136
- self.model = model
137
219
  self.timeout = timeout
138
220
  self.max_retries = max_retries
139
221
  self.client: Optional[anthropic.Anthropic]
@@ -142,66 +224,66 @@ class AnthropicClient:
142
224
  else:
143
225
  self.client = None
144
226
 
145
- async def request_completions(self, messages: List[ChatCompletionMessageParam],
146
- response_format_info: Optional[ResponseFormatInfo] = None,
147
- message_type: MessageType = MessageType.CHAT) -> Any:
227
+ async def request_completions(
228
+ self, messages: List[ChatCompletionMessageParam],
229
+ model: str,
230
+ response_format_info: Optional[ResponseFormatInfo] = None,
231
+ message_type: MessageType = MessageType.CHAT
232
+ ) -> Any:
148
233
  """
149
234
  Get a response from Claude or the Mito server that adheres to the AgentResponse format.
150
235
  """
151
- try:
152
- anthropic_system_prompt, anthropic_messages = get_anthropic_system_prompt_and_messages(messages)
236
+ anthropic_system_prompt, anthropic_messages = get_anthropic_system_prompt_and_messages_with_caching(messages)
237
+
238
+ provider_data = get_anthropic_completion_function_params(
239
+ message_type=message_type,
240
+ model=model,
241
+ messages=anthropic_messages,
242
+ max_tokens=MAX_TOKENS,
243
+ temperature=0,
244
+ system=anthropic_system_prompt,
245
+ stream=None,
246
+ response_format_info=response_format_info
247
+ )
248
+
249
+ if self.api_key:
250
+ # Unpack provider_data for direct API call
251
+ assert self.client is not None
252
+ response = self.client.messages.create(**provider_data)
153
253
 
154
- provider_data = get_anthropic_completion_function_params(
155
- model=self.model if response_format_info else ANTHROPIC_FAST_MODEL,
156
- messages=anthropic_messages,
157
- max_tokens=MAX_TOKENS,
158
- temperature=0,
159
- system=anthropic_system_prompt,
160
- stream=None,
161
- response_format_info=response_format_info
162
- )
163
-
164
- if self.api_key:
165
- # Unpack provider_data for direct API call
166
- assert self.client is not None
167
- response = self.client.messages.create(**provider_data)
168
- if provider_data.get("tool_choice") is not None:
169
- result = extract_and_parse_anthropic_json_response(response)
170
- return json.dumps(result) if not isinstance(result, str) else result
171
- else:
172
- content = response.content
173
- if content[0].type == "text":
174
- return content[0].text
175
- else:
176
- return ""
254
+ if provider_data.get("tool_choice") is not None:
255
+ result = extract_and_parse_anthropic_json_response(response)
256
+ return json.dumps(result) if not isinstance(result, str) else result
177
257
  else:
178
- # Only pass provider_data to the server
179
- response = await get_anthropic_completion_from_mito_server(
180
- model=provider_data["model"],
181
- max_tokens=provider_data["max_tokens"],
182
- temperature=provider_data["temperature"],
183
- system=provider_data["system"],
184
- messages=provider_data["messages"],
185
- tools=provider_data.get("tools"),
186
- tool_choice=provider_data.get("tool_choice"),
187
- message_type=message_type
188
- )
189
- return response
190
- except anthropic.RateLimitError:
191
- raise Exception("Rate limit exceeded. Please try again later or reduce your request frequency.")
192
- except Exception as e:
193
- return f"Error streaming content: {str(e)}"
258
+ content = response.content
259
+ if content[0].type == "text":
260
+ return content[0].text
261
+ else:
262
+ return ""
263
+ else:
264
+ # Only pass provider_data to the server
265
+ response = await get_anthropic_completion_from_mito_server(
266
+ model=provider_data["model"],
267
+ max_tokens=provider_data["max_tokens"],
268
+ temperature=provider_data["temperature"],
269
+ system=provider_data["system"],
270
+ messages=provider_data["messages"],
271
+ tools=provider_data.get("tools"),
272
+ tool_choice=provider_data.get("tool_choice"),
273
+ message_type=message_type
274
+ )
275
+ return response
194
276
 
195
- async def stream_response(self, messages: List[ChatCompletionMessageParam], message_id: str, message_type: MessageType,
277
+ async def stream_completions(self, messages: List[ChatCompletionMessageParam], model: str, message_id: str, message_type: MessageType,
196
278
  reply_fn: Callable[[Union[CompletionReply, CompletionStreamChunk]], None]) -> str:
197
279
  try:
198
- anthropic_system_prompt, anthropic_messages = get_anthropic_system_prompt_and_messages(messages)
280
+ anthropic_system_prompt, anthropic_messages = get_anthropic_system_prompt_and_messages_with_caching(messages)
199
281
  accumulated_response = ""
200
282
 
201
283
  if self.api_key:
202
284
  assert self.client is not None
203
285
  stream = self.client.messages.create(
204
- model=self.model,
286
+ model=model,
205
287
  max_tokens=MAX_TOKENS,
206
288
  temperature=0,
207
289
  system=anthropic_system_prompt,
@@ -209,7 +291,6 @@ class AnthropicClient:
209
291
  stream=True
210
292
  )
211
293
 
212
-
213
294
  for chunk in stream:
214
295
  if chunk.type == "content_block_delta" and chunk.delta.type == "text_delta":
215
296
  content = chunk.delta.text
@@ -229,24 +310,17 @@ class AnthropicClient:
229
310
 
230
311
  else:
231
312
  async for stram_chunk in stream_anthropic_completion_from_mito_server(
232
- model=self.model,
313
+ model=model,
233
314
  max_tokens=MAX_TOKENS,
234
315
  temperature=0,
235
316
  system=anthropic_system_prompt,
236
317
  messages=anthropic_messages,
237
318
  stream=True,
238
- message_type=message_type
319
+ message_type=message_type,
320
+ reply_fn=reply_fn,
321
+ message_id=message_id
239
322
  ):
240
323
  accumulated_response += stram_chunk
241
- reply_fn(CompletionStreamChunk(
242
- parent_id=message_id,
243
- chunk=CompletionItem(
244
- content=stram_chunk,
245
- isIncomplete=True,
246
- token=message_id,
247
- ),
248
- done=False,
249
- ))
250
324
 
251
325
  return accumulated_response
252
326
 
@@ -254,6 +328,7 @@ class AnthropicClient:
254
328
  raise Exception("Rate limit exceeded. Please try again later or reduce your request frequency.")
255
329
 
256
330
  except Exception as e:
257
- return f"Error streaming content: {str(e)}"
331
+ print(f"Error streaming content: {str(e)}")
332
+ raise e
258
333
 
259
334
 
@@ -3,4 +3,4 @@
3
3
 
4
4
  """App builder module for Mito AI."""
5
5
 
6
- from .handlers import AppBuilderHandler
6
+ from .handlers import AppDeployHandler
@@ -0,0 +1,44 @@
1
+ # Copyright (c) Saga Inc.
2
+ # Distributed under the terms of the GNU Affero General Public License v3.0 License.
3
+
4
+ import os
5
+ import zipfile
6
+ import logging
7
+ from typing import List, Optional
8
+
9
+ from mito_ai.path_utils import AbsoluteNotebookDirPath
10
+
11
+ def add_files_to_zip(
12
+ zip_path: str,
13
+ notebook_dir_path: AbsoluteNotebookDirPath,
14
+ files_to_add: List[str],
15
+ app_file_name: str,
16
+ logger: Optional[logging.Logger] = None
17
+ ) -> None:
18
+ """Create a zip file at zip_path and add the selected files/folders."""
19
+ with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zipf:
20
+ for file_to_add_rel_path in files_to_add:
21
+
22
+ file_to_add_abs_path = os.path.join(notebook_dir_path, file_to_add_rel_path)
23
+
24
+ if os.path.isfile(file_to_add_abs_path):
25
+ basename = os.path.basename(file_to_add_abs_path)
26
+
27
+ if basename == app_file_name:
28
+ # For the actual app file, we want to write it just as app.py
29
+ # so our infra can always deploy using `streamlit run app.py`
30
+ # without having to account for different app names
31
+ zipf.write(file_to_add_abs_path, arcname='app.py')
32
+ else:
33
+ # otherwise we want to keep the name as is so all references
34
+ # to it from the app are correct
35
+ zipf.write(file_to_add_abs_path, arcname=file_to_add_rel_path)
36
+ elif os.path.isdir(file_to_add_abs_path):
37
+ for root, _, files in os.walk(file_to_add_abs_path):
38
+ for file in files:
39
+ file_abs = os.path.join(root, file)
40
+ arcname = os.path.relpath(file_abs, notebook_dir_path)
41
+ zipf.write(file_abs, arcname=arcname)
42
+ else:
43
+ if logger:
44
+ logger.warning(f"Skipping missing file: {file_to_add_abs_path}")