mito-ai 0.1.46__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 (74) hide show
  1. mito_ai/_version.py +1 -1
  2. mito_ai/app_deploy/app_deploy_utils.py +28 -9
  3. mito_ai/app_deploy/handlers.py +123 -84
  4. mito_ai/app_deploy/models.py +19 -12
  5. mito_ai/completions/models.py +6 -1
  6. mito_ai/completions/prompt_builders/agent_execution_prompt.py +13 -1
  7. mito_ai/completions/prompt_builders/agent_system_message.py +63 -4
  8. mito_ai/completions/prompt_builders/chat_system_message.py +4 -0
  9. mito_ai/completions/prompt_builders/prompt_constants.py +1 -0
  10. mito_ai/completions/prompt_builders/utils.py +13 -0
  11. mito_ai/path_utils.py +70 -0
  12. mito_ai/streamlit_conversion/agent_utils.py +4 -201
  13. mito_ai/streamlit_conversion/prompts/prompt_constants.py +142 -152
  14. mito_ai/streamlit_conversion/prompts/streamlit_error_correction_prompt.py +3 -3
  15. mito_ai/streamlit_conversion/prompts/streamlit_finish_todo_prompt.py +2 -2
  16. mito_ai/streamlit_conversion/prompts/update_existing_app_prompt.py +2 -2
  17. mito_ai/streamlit_conversion/search_replace_utils.py +94 -0
  18. mito_ai/streamlit_conversion/streamlit_agent_handler.py +35 -46
  19. mito_ai/streamlit_conversion/streamlit_utils.py +13 -75
  20. mito_ai/streamlit_conversion/validate_streamlit_app.py +6 -21
  21. mito_ai/streamlit_preview/__init__.py +1 -2
  22. mito_ai/streamlit_preview/handlers.py +54 -85
  23. mito_ai/streamlit_preview/manager.py +11 -18
  24. mito_ai/streamlit_preview/utils.py +12 -28
  25. mito_ai/tests/deploy_app/test_app_deploy_utils.py +22 -4
  26. mito_ai/tests/message_history/test_message_history_utils.py +3 -0
  27. mito_ai/tests/streamlit_conversion/test_apply_search_replace.py +240 -0
  28. mito_ai/tests/streamlit_conversion/test_streamlit_agent_handler.py +40 -60
  29. mito_ai/tests/streamlit_conversion/test_streamlit_utils.py +26 -29
  30. mito_ai/tests/streamlit_conversion/test_validate_streamlit_app.py +25 -20
  31. mito_ai/tests/streamlit_preview/test_streamlit_preview_handler.py +87 -57
  32. mito_ai/tests/streamlit_preview/test_streamlit_preview_manager.py +27 -40
  33. mito_ai/user/handlers.py +15 -3
  34. mito_ai/utils/create.py +17 -1
  35. mito_ai/utils/error_classes.py +42 -0
  36. mito_ai/utils/message_history_utils.py +3 -1
  37. mito_ai/utils/telemetry_utils.py +78 -13
  38. {mito_ai-0.1.46.data → mito_ai-0.1.49.data}/data/share/jupyter/labextensions/mito_ai/build_log.json +100 -100
  39. {mito_ai-0.1.46.data → mito_ai-0.1.49.data}/data/share/jupyter/labextensions/mito_ai/package.json +2 -2
  40. {mito_ai-0.1.46.data → mito_ai-0.1.49.data}/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/package.json.orig +1 -1
  41. mito_ai-0.1.46.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.20f12766ecd3d430568e.js → mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.8f1845da6bf2b128c049.js +3571 -1442
  42. mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.8f1845da6bf2b128c049.js.map +1 -0
  43. mito_ai-0.1.46.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.54126ab6511271265443.js → mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.8b24b5b3b93f95205b56.js +24 -24
  44. mito_ai-0.1.46.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.54126ab6511271265443.js.map → mito_ai-0.1.49.data/data/share/jupyter/labextensions/mito_ai/static/remoteEntry.8b24b5b3b93f95205b56.js.map +1 -1
  45. {mito_ai-0.1.46.dist-info → mito_ai-0.1.49.dist-info}/METADATA +1 -1
  46. {mito_ai-0.1.46.dist-info → mito_ai-0.1.49.dist-info}/RECORD +71 -69
  47. mito_ai/tests/streamlit_conversion/test_apply_patch_to_text.py +0 -368
  48. mito_ai/tests/streamlit_conversion/test_fix_diff_headers.py +0 -533
  49. mito_ai-0.1.46.data/data/share/jupyter/labextensions/mito_ai/static/lib_index_js.20f12766ecd3d430568e.js.map +0 -1
  50. /mito_ai/streamlit_conversion/{streamlit_system_prompt.py → prompts/streamlit_system_prompt.py} +0 -0
  51. {mito_ai-0.1.46.data → mito_ai-0.1.49.data}/data/etc/jupyter/jupyter_server_config.d/mito_ai.json +0 -0
  52. {mito_ai-0.1.46.data → mito_ai-0.1.49.data}/data/share/jupyter/labextensions/mito_ai/schemas/mito_ai/toolbar-buttons.json +0 -0
  53. {mito_ai-0.1.46.data → mito_ai-0.1.49.data}/data/share/jupyter/labextensions/mito_ai/static/node_modules_process_browser_js.4b128e94d31a81ebd209.js +0 -0
  54. {mito_ai-0.1.46.data → mito_ai-0.1.49.data}/data/share/jupyter/labextensions/mito_ai/static/node_modules_process_browser_js.4b128e94d31a81ebd209.js.map +0 -0
  55. {mito_ai-0.1.46.data → mito_ai-0.1.49.data}/data/share/jupyter/labextensions/mito_ai/static/style.js +0 -0
  56. {mito_ai-0.1.46.data → mito_ai-0.1.49.data}/data/share/jupyter/labextensions/mito_ai/static/style_index_js.5876024bb17dbd6a3ee6.js +0 -0
  57. {mito_ai-0.1.46.data → mito_ai-0.1.49.data}/data/share/jupyter/labextensions/mito_ai/static/style_index_js.5876024bb17dbd6a3ee6.js.map +0 -0
  58. {mito_ai-0.1.46.data → 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 +0 -0
  59. {mito_ai-0.1.46.data → 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 +0 -0
  60. {mito_ai-0.1.46.data → 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 +0 -0
  61. {mito_ai-0.1.46.data → 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 +0 -0
  62. {mito_ai-0.1.46.data → mito_ai-0.1.49.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_aws-amplify_dist_esm_index_mjs.6bac1a8c4cc93f15f6b7.js +0 -0
  63. {mito_ai-0.1.46.data → 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 +0 -0
  64. {mito_ai-0.1.46.data → 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 +0 -0
  65. {mito_ai-0.1.46.data → 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 +0 -0
  66. {mito_ai-0.1.46.data → 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 +0 -0
  67. {mito_ai-0.1.46.data → 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 +0 -0
  68. {mito_ai-0.1.46.data → mito_ai-0.1.49.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.3f6754ac5116d47de76b.js +0 -0
  69. {mito_ai-0.1.46.data → mito_ai-0.1.49.data}/data/share/jupyter/labextensions/mito_ai/static/vendors-node_modules_semver_index_js.3f6754ac5116d47de76b.js.map +0 -0
  70. {mito_ai-0.1.46.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
  71. {mito_ai-0.1.46.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
  72. {mito_ai-0.1.46.dist-info → mito_ai-0.1.49.dist-info}/WHEEL +0 -0
  73. {mito_ai-0.1.46.dist-info → mito_ai-0.1.49.dist-info}/entry_points.txt +0 -0
  74. {mito_ai-0.1.46.dist-info → mito_ai-0.1.49.dist-info}/licenses/LICENSE +0 -0
@@ -19,6 +19,7 @@ ACTIVE_CELL_ID_SECTION_HEADING = "The ID of the active code cell:"
19
19
  ACTIVE_CELL_OUTPUT_SECTION_HEADING = "Output of the active code cell:"
20
20
  GET_CELL_OUTPUT_TOOL_RESPONSE_SECTION_HEADING = "Output of the code cell you just applied the CELL_UPDATE to:"
21
21
  JUPYTER_NOTEBOOK_SECTION_HEADING = "Jupyter Notebook:"
22
+ STREAMLIT_APP_STATUS_SECTION_HEADING = "Streamlit App Status:"
22
23
 
23
24
  # Placeholder text used when trimming content from messages
24
25
  CONTENT_REMOVED_PLACEHOLDER = "Content removed to save space"
@@ -69,3 +69,16 @@ def get_selected_context_str(additional_context: Optional[List[Dict[str, str]]])
69
69
 
70
70
  # STEP 3: Combine into a single string
71
71
  return "\n\n".join(context_parts)
72
+
73
+
74
+ def get_streamlit_app_status_str(notebook_id: str, notebook_path: str) -> str:
75
+ """
76
+ Get the streamlit app status string.
77
+ """
78
+ from mito_ai.path_utils import does_notebook_id_have_corresponding_app
79
+ if does_notebook_id_have_corresponding_app(notebook_id, notebook_path):
80
+ return "The notebook has an existing Streamlit app that you can edit"
81
+ return "The notebook does not have an existing Streamlit app. If you want to show an app to the user, you must create a new one."
82
+
83
+
84
+
mito_ai/path_utils.py ADDED
@@ -0,0 +1,70 @@
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 NewType
5
+ import os
6
+ from mito_ai.utils.error_classes import StreamlitPreviewError
7
+
8
+ # Type definitions for better type safety
9
+ AbsoluteNotebookPath = NewType('AbsoluteNotebookPath', str)
10
+ AbsoluteNotebookDirPath = NewType('AbsoluteNotebookDirPath', str)
11
+ AbsoluteAppPath = NewType('AbsoluteAppPath', str)
12
+ AppFileName = NewType("AppFileName", str)
13
+
14
+ def get_absolute_notebook_path(notebook_path: str) -> AbsoluteNotebookPath:
15
+ """
16
+ Convert any notebook path to an absolute path.
17
+
18
+ Args:
19
+ notebook_path: Path to the notebook (can be relative or absolute)
20
+
21
+ Returns:
22
+ AbsoluteNotebookPath: The absolute path to the notebook
23
+
24
+ Raises:
25
+ ValueError: If the path is invalid or empty
26
+ """
27
+ if not notebook_path or not notebook_path.strip():
28
+ raise StreamlitPreviewError("Notebook path cannot be empty", 400)
29
+
30
+ absolute_path = os.path.abspath(notebook_path)
31
+ return AbsoluteNotebookPath(absolute_path)
32
+
33
+
34
+ def get_absolute_notebook_dir_path(notebook_path: AbsoluteNotebookPath) -> AbsoluteNotebookDirPath:
35
+ """
36
+ Get the absolute directory containing the notebook.
37
+ """
38
+ return AbsoluteNotebookDirPath(os.path.dirname(notebook_path))
39
+
40
+ def get_absolute_app_path(app_directory: AbsoluteNotebookDirPath, app_file_name: AppFileName) -> AbsoluteAppPath:
41
+ """
42
+ Get the absolute path to the app
43
+ """
44
+ return AbsoluteAppPath(os.path.join(app_directory, app_file_name))
45
+
46
+ def get_app_file_name(notebook_id: str) -> AppFileName:
47
+ """
48
+ Converts the notebook id into the corresponding app id
49
+ """
50
+ mito_app_name = notebook_id.replace('mito-notebook-', 'mito-app-')
51
+ return AppFileName(f'{mito_app_name}.py')
52
+
53
+ def does_app_path_exist(app_path: AbsoluteAppPath) -> bool:
54
+ """
55
+ Check if the app file exists
56
+ """
57
+ return os.path.exists(app_path)
58
+
59
+ def does_notebook_id_have_corresponding_app(notebook_id: str, notebook_path: str) -> bool:
60
+ """
61
+ Given a notebook_id and raw notebook_path checks if the notebook has a corresponding
62
+ app by converting the notebook_path into an absolute path and converting the notebook_id
63
+ into an app name
64
+ """
65
+
66
+ app_file_name = get_app_file_name(notebook_id)
67
+ notebook_path = get_absolute_notebook_path(notebook_path)
68
+ app_directory = get_absolute_notebook_dir_path(notebook_path)
69
+ app_path = get_absolute_app_path(app_directory, app_file_name)
70
+ return does_app_path_exist(app_path)
@@ -1,221 +1,24 @@
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
- from typing import List
4
+ from typing import List, Tuple
5
5
  import re
6
6
  from anthropic.types import MessageParam
7
- from mito_ai.streamlit_conversion.streamlit_system_prompt import streamlit_system_prompt
7
+ from mito_ai.streamlit_conversion.prompts.streamlit_system_prompt import streamlit_system_prompt
8
8
  from mito_ai.utils.anthropic_utils import stream_anthropic_completion_from_mito_server
9
- from unidiff import PatchSet
10
9
  from mito_ai.streamlit_conversion.prompts.prompt_constants import MITO_TODO_PLACEHOLDER
11
10
  from mito_ai.completions.models import MessageType
12
11
 
13
- STREAMLIT_AI_MODEL = "claude-3-5-haiku-latest"
12
+ STREAMLIT_AI_MODEL = "claude-sonnet-4-5-20250929"
14
13
 
15
14
  def extract_todo_placeholders(agent_response: str) -> List[str]:
16
15
  """Extract TODO placeholders from the agent's response"""
17
16
  return [line.strip() for line in agent_response.split('\n') if MITO_TODO_PLACEHOLDER in line]
18
17
 
19
-
20
- def apply_patch_to_text(text: str, diff: str) -> str:
21
- """
22
- Apply a *unified-diff* (git-style) patch to the given text and return
23
- the updated contents.
24
-
25
- Parameters
26
- ----------
27
- text : str
28
- The original file contents.
29
- diff : str
30
- A unified diff that transforms *text* into the desired output.
31
- The diff must reference exactly one file (the Streamlit app).
32
- NOTE: This assumes a custom format where BOTH -X,Y and +X,Y
33
- reference the original file line numbers.
34
-
35
- Returns
36
- -------
37
- str
38
- The patched contents.
39
-
40
- Raises
41
- ------
42
- ValueError
43
- If the patch cannot be applied or references more than one file.
44
- """
45
- # Nothing to do
46
- if not diff.strip():
47
- return text
48
-
49
- # Parse the patch
50
- patch = PatchSet(diff.splitlines(keepends=True))
51
-
52
- # We expect a single-file patch (what the prompt asks the model to emit)
53
- if len(patch) == 0:
54
- raise ValueError("No patches found in diff")
55
-
56
- # Check that all patches are for the same file
57
- file_names = set(p.source_file for p in patch)
58
- if len(file_names) > 1:
59
- raise ValueError(
60
- f"Expected patches for exactly one file, got files: {file_names}"
61
- )
62
-
63
- # Apply all hunks from all patches (they should all be for the same file)
64
- original_lines = text.splitlines(keepends=True)
65
- result_lines: List[str] = []
66
- cursor = 0 # index in original_lines (0-based)
67
-
68
- # Process all hunks from all patches
69
- # We only expect one patch file, but it always returns as a list
70
- # so we just iterate over it
71
- for file_patch in patch:
72
- for hunk in file_patch:
73
- # Since hunks reference the original file, just convert to 0-based
74
- hunk_start = hunk.source_start - 1
75
-
76
- # Copy unchanged lines before this hunk
77
- while cursor < hunk_start:
78
- if cursor < len(original_lines):
79
- result_lines.append(original_lines[cursor])
80
- cursor += 1
81
-
82
- # Apply hunk line-by-line
83
- for line in hunk:
84
- if line.is_context:
85
- # Use the line from the diff to preserve exact formatting
86
- result_lines.append(line.value)
87
- cursor += 1
88
- elif line.is_removed:
89
- cursor += 1 # Skip this line from the original
90
- elif line.is_added:
91
- # Use the line from the diff to preserve exact formatting
92
- result_lines.append(line.value)
93
-
94
- # Copy any remaining lines after the last hunk
95
- result_lines.extend(original_lines[cursor:])
96
-
97
- return "".join(result_lines)
98
-
99
-
100
- def fix_context_lines(diff: str) -> str:
101
- """
102
- Fix context lines in unified diff to ensure they all start with a space character.
103
-
104
- In unified diffs, context lines (unchanged lines) must start with a single space ' ',
105
- even if the line itself is empty. The AI sometimes generates diffs where empty
106
- context lines are just blank lines without the leading space, which causes the
107
- unidiff parser to fail.
108
-
109
- Args:
110
- diff (str): The unified diff string
111
-
112
- Returns:
113
- str: The corrected diff with proper context line formatting
114
- """
115
- lines = diff.split('\n')
116
- corrected_lines = []
117
- in_hunk = False
118
-
119
- for i, line in enumerate(lines):
120
- # Check if we're entering a hunk
121
- if line.startswith('@@'):
122
- in_hunk = True
123
- corrected_lines.append(line)
124
- continue
125
-
126
- # Check if we're leaving a hunk (new file header)
127
- if line.startswith('---') or line.startswith('+++'):
128
- in_hunk = False
129
- corrected_lines.append(line)
130
- continue
131
-
132
- if in_hunk:
133
- # We're inside a hunk
134
- if line.startswith(' ') or line.startswith('-') or line.startswith('+'):
135
- # Already has proper diff marker
136
- corrected_lines.append(line)
137
- elif line.strip() == '':
138
- # Empty line should be a context line with leading space
139
- corrected_lines.append(' ')
140
- else:
141
- # Line without diff marker - treat as context line
142
- corrected_lines.append(' ' + line)
143
- else:
144
- # Outside hunk - keep as is
145
- corrected_lines.append(line)
146
-
147
- return '\n'.join(corrected_lines)
148
-
149
-
150
- def fix_diff_headers(diff: str) -> str:
151
- """
152
- The AI is generally not very good at counting the number of lines in the diff. If the hunk header has
153
- an incorrect count, then the patch will fail. So instead we just calculate the counts ourselves, its deterministic.
154
-
155
- If no header is provided at all, then there is nothing to fix.
156
- """
157
- # First fix context lines to ensure they have proper leading spaces
158
- diff = fix_context_lines(diff)
159
-
160
- lines = diff.split('\n')
161
-
162
- for i, line in enumerate(lines):
163
- if line.startswith('@@'):
164
- # Extract the starting line numbers
165
- match = re.match(r'@@ -(\d+),\d+ \+(\d+),\d+ @@', line)
166
- if match:
167
- old_start = match.group(1)
168
- new_start = match.group(2)
169
-
170
- # Count lines in this hunk
171
- old_count = 0
172
- new_count = 0
173
-
174
- # Find the end of this hunk (next @@ line or end of file)
175
- hunk_end = len(lines)
176
- for j in range(i + 1, len(lines)):
177
- if lines[j].startswith('@@'):
178
- hunk_end = j
179
- break
180
-
181
- # Count lines in this hunk
182
- for j in range(i + 1, hunk_end):
183
- hunk_line = lines[j]
184
- # Empty lines are treated as context lines
185
- if hunk_line == '' or hunk_line.startswith(' ') or hunk_line.startswith('-'):
186
- old_count += 1
187
- if hunk_line == '' or hunk_line.startswith(' ') or hunk_line.startswith('+'):
188
- new_count += 1
189
-
190
- # Replace the header with correct counts
191
- lines[i] = f"@@ -{old_start},{old_count} +{new_start},{new_count} @@"
192
-
193
- corrected_diff = '\n'.join(lines)
194
- corrected_diff = corrected_diff.lstrip()
195
-
196
- # If there is no diff, just return it without fixing file headers
197
- if len(corrected_diff) == 0:
198
- return corrected_diff
199
-
200
- # Remove known problametic file component headers that the AI sometimes returns
201
- problamatic_file_header_components = ['--- a/app.py +++ b/app.py']
202
- for problamatic_file_header_component in problamatic_file_header_components:
203
- corrected_diff = corrected_diff.removeprefix(problamatic_file_header_component).lstrip()
204
-
205
-
206
- # If the diff is missing the file component of the header, add it
207
- valid_header_component = """--- a/app.py
208
- +++ b/app.py"""
209
- if not corrected_diff.startswith(valid_header_component):
210
- corrected_diff = valid_header_component + '\n' + corrected_diff
211
-
212
- return corrected_diff
213
-
214
-
215
18
  async def get_response_from_agent(message_to_agent: List[MessageParam]) -> str:
216
19
  """Gets the streaming response from the agent using the mito server"""
217
20
  model = STREAMLIT_AI_MODEL
218
- max_tokens = 8192 # 64_000
21
+ max_tokens = 64000 # TODO: If we move to haiku, we must reset this to 8192
219
22
  temperature = 0.2
220
23
 
221
24
  accumulated_response = ""
@@ -3,121 +3,154 @@
3
3
 
4
4
  MITO_TODO_PLACEHOLDER = "# MITO_TODO_PLACEHOLDER"
5
5
 
6
- unified_diff_instructions = f"""
7
- RESPONSE FORMAT: Return the changes you want to make to the streamlit app as a **unified diff (git-style patch)**:
8
-
9
- A unified diff looks is the following and tells the system which lines of code to add, remove, or modify:
10
- --- a/app.py
11
- +++ b/app.py
12
- @@ -START_LINE,1 +START_LINE,1 @@
13
- x = 1
14
- -y = 2
15
- +y = 3
16
-
17
- The components of the unified diff are the following:
18
- - `--- a/app.py` -> The original file. We will always use the file app.py
19
- - `+++ b/app.py` -> The modified file. We will always use the file app.py
20
- - `@@ -START_LINE,1 +START_LINE,1 @@` -> The hunk header
21
- - `x = 1` -> The original context line
22
- - `-y = 2` -> The removed line
23
- - `+y = 3` -> The added line
24
-
25
- When you create a unified diff, you must follow the following format:
26
- - Begin with a ```unified_diff marker and a ``` end marker.
27
- - Always, include the standard header. On line 1: `--- a/app.py` and on line 2: `+++ b/app.py` like in the example above.
28
- - Show only the modified hunks; each hunk must start with an `@@` header with line numbers.
29
- - Within each hunk:
30
- * Unchanged context lines start with a single space ` `.
31
- * Removed lines start with `-`.
32
- * Added lines start with `+`.
33
- - If there are **no changes**, return an empty string.
34
- - Do not include the line numbers in your response.
35
-
36
- **CRITICAL: INDENTATION HANDLING**
37
- When modifying indented code (like content inside tabs, functions, or loops), you MUST:
38
- - **Preserve exact indentation levels** in your added lines
39
- - **Show the complete indentation change** when moving code between indentation levels
40
- - **Include all whitespace** in your diff - indentation is part of the code structure
41
- - When unindenting code (removing tabs/context), show the original indented line with `-` and the unindented version with `+`
42
- - When indenting code (adding tabs/context), show the original unindented line with `-` and the indented version with `+`
43
-
44
- **HUNK HEADER FORMAT:**
45
- Use `@@ -START_LINE,1 +START_LINE,1 @@` where:
46
- - START_LINE is the line number in the **original file** where this hunk begins
47
- - Always use `1` for both count values (the system will calculate correct counts later)
48
- - All line numbers must reference the **original file**, not the modified version
49
- - For example, if the hunk begins on line 12, use `@@ -12,1 +12,1 @@`
50
-
51
- **WRONG FORMATS (DO NOT USE):**
52
- `@@ -12:` -> This is wrong because it is using a colon, doesn't have the count value, and doesn't have both sets of start_line numbers and lines counts.
53
- `@@ -12,1` -> This is wrong because it doesn't have both sets of start_line numbers and lines counts.
54
- `@@ 12,1 12,1 @@` -> This is wrong because it doesn't use - and + before the start_line
55
- `@@-12,1 +12,1@@` -> This is wrong because it doesn't have a space after the first @@ and doesn't have a space before the second @@.
56
-
57
- **MULTIPLE HUNKS:**
58
- - If changes are separated by 5+ unchanged lines, create separate hunks
59
- - Each hunk needs its own `@@` header with the correct START_LINE for that section
60
- - Hunks must be in ascending order by line number
61
-
62
- <Example 1: Single change in middle of file>
63
-
64
- Assume `data_list = [` is on line 57 of the original file:
65
- ```unified_diff
66
- --- a/app.py
67
- +++ b/app.py
68
- @@ -57,1 +57,1 @@
69
- data_list = [
70
- - {{'id': 1, 'name': 'Old'}},
71
- + {{'id': 1, 'name': 'New'}},
72
- + {{'id': 2, 'name': 'Also New'}},
6
+ search_replace_instructions = f"""
7
+ RESPONSE FORMAT: You can edit the existing code using the **SEARCH_REPLACE format** for exact string matching and replacement.
8
+
9
+ **STRUCTURE:**
10
+ ```search_replace
11
+ >>>>>>> SEARCH
12
+ [exact code currently in the file]
13
+ =======
14
+ [new code to replace it with]
15
+ <<<<<<< REPLACE
16
+ ```
17
+
18
+ **COMPONENTS:**
19
+ ```search_replace - This is the start of the search/replace block
20
+ - `>>>>>>> SEARCH` - Exact text that EXISTS NOW in the file (7 chevrons)
21
+ - `=======` - Separator between the search and replace blocks (7 equals signs)
22
+ - `<<<<<<< REPLACE` - Replacement text (7 chevrons)
23
+
24
+ ---
25
+
26
+ **CRITICAL RULES - READ CAREFULLY:**
27
+
28
+ 1. **SEARCH = CURRENT STATE ONLY**
29
+ - The SEARCH block must contain ONLY code that currently exists in the file
30
+ - NEVER include new code, future code, or code you wish existed in the SEARCH block
31
+ - Copy exact text from the current file, character-for-character
32
+
33
+ 2. **EXACT MATCHING REQUIRED**
34
+ - Every space, tab, newline must match perfectly
35
+ - Preserve exact indentation (spaces vs tabs)
36
+ - Include trailing newlines if present
37
+ - No approximations - even one character difference will fail
38
+
39
+ 3. **SIZE LIMITS**
40
+ - There are no size limits to each search/replace block, however, it is generally preferable to keep the SEARCH blocks small and focused on one change.
41
+ - For large changes, use multiple smaller search/replace blocks
42
+
43
+ 4. **UNIQUENESS**
44
+ - Include enough context to make the SEARCH block unique
45
+ - If text appears multiple times, add surrounding lines
46
+ - Ensure there's only ONE match in the file
47
+
48
+ 5. **VERIFICATION CHECKLIST** (before generating each block):
49
+ Is every line in my SEARCH block currently in the file?
50
+ ✓ Did I copy the exact spacing and whitespace?
51
+ Will this match exactly once?
52
+
53
+ 6. **SEARCH REPLACE BLOCK STRUCTURE**
54
+ - You must adhere to to the exact search_replace structure as shown in the examples.
55
+
56
+ ---
57
+
58
+ **MULTIPLE REPLACEMENTS:**
59
+ - You can include multiple search/replace blocks in one response
60
+ - Each block is independent and processed separately
61
+ - Use separate ```search_replace blocks for each change
62
+
63
+ <Example 1: Updating existing content>
64
+
65
+ ```search_replace
66
+ >>>>>>> SEARCH
67
+ st.title("Old Title")
68
+ =======
69
+ st.title("New Title")
70
+ <<<<<<< REPLACE
73
71
  ```
74
72
  </Example 1>
75
73
 
76
- <Example 2: Multiple separate changes>
77
- Assume the original file has:
78
-
79
- Line 5: import os
80
- Line 30: def process():
81
-
82
- ```unified_diff
83
- --- a/app.py
84
- +++ b/app.py
85
- @@ -5,1 +5,1 @@
86
- import os
87
- +import sys
88
- @@ -30,1 +30,1 @@
89
- -def process():
90
- +def process_data():
74
+ <Example 2: Adding new content>
75
+
76
+ ```search_replace
77
+ >>>>>>> SEARCH
78
+ st.title("My App")
79
+ =======
80
+ st.title("My App")
81
+ st.header("Welcome")
82
+ st.write("This is a test app")
83
+ <<<<<<< REPLACE
91
84
  ```
92
85
  </Example 2>
93
86
 
94
- <Example 3: Adding multiple entries to a list while respecting indentations>
95
-
96
- In the example below, assume that the line of code `data_list = [` is on line 57 of the existing streamlit app.
97
-
98
- ```unified_diff
99
- --- a/app.py
100
- +++ b/app.py
101
- @@ -57,1 +57,1 @@
102
- data_list = [
103
- {{'id': 1, 'name': 'Item A', 'category': 'Type 1', 'value': 100}},
104
- {{'id': 2, 'name': 'Item B', 'category': 'Type 2', 'value': 200}},
105
- - {MITO_TODO_PLACEHOLDER}: Add remaining entries from notebook
106
- + {{'id': 3, 'name': 'Item C', 'category': 'Type 3', 'value': 300}},
107
- + {{'id': 4, 'name': 'Item D', 'category': 'Type 4', 'value': 400}},
108
- + {{'id': 5, 'name': 'Item E', 'category': 'Type 5', 'value': 500}},
109
- + {{'id': 6, 'name': 'Item F', 'category': 'Type 6', 'value': 600}},
110
- + {{'id': 7, 'name': 'Item G', 'category': 'Type 7', 'value': 700}},
111
- + {{'id': 8, 'name': 'Item H', 'category': 'Type 8', 'value': 800}},
112
- + {{'id': 9, 'name': 'Item I', 'category': 'Type 9', 'value': 900}},
113
- + {{'id': 10, 'name': 'Item J', 'category': 'Type 10', 'value': 1000}}
87
+ <Example 3: Deleting existing content>
88
+
89
+ ```search_replace
90
+ >>>>>>> SEARCH
91
+ st.write("Old message")
92
+ =======
93
+ <<<<<<< REPLACE
114
94
  ```
115
95
  </Example 3>
116
96
 
117
- <Example 4: Consolidating tabs - removing tab structure and unindenting content>
97
+ <Example 4: Multiple replacements in one response>
98
+
99
+ ```search_replace
100
+ >>>>>>> SEARCH
101
+ st.title("Old Title")
102
+ =======
103
+ st.title("New Title")
104
+ <<<<<<< REPLACE
105
+ ```
106
+
107
+ ```search_replace
108
+ >>>>>>> SEARCH
109
+ st.write("Old message")
110
+ =======
111
+ st.write("New message")
112
+ <<<<<<< REPLACE
113
+ ```
114
+ </Example 4>
115
+
116
+ <Example 5: Using extra context to identify the correct code to replace>
117
+
118
+ In the below example, assume that the code st.write("Old message") appears multiple times in the file, so we use extra context lines to identify the correct code to replace.
119
+
120
+ ```search_replace
121
+ >>>>>>> SEARCH
122
+ # This is a unique comment
123
+ st.write("Old message")
124
+ =======
125
+ # This is a unique comment
126
+ st.write("New message")
127
+ <<<<<<< REPLACE
128
+ ```
129
+ </Example 5>
130
+
131
+ <Example 6: Search/replace while respecting whitespace and indentation>
132
+
133
+ ```search_replace
134
+ >>>>>>> SEARCH
135
+ data_list = [
136
+ {{'id': 1, 'name': 'Item A'}},
137
+ {MITO_TODO_PLACEHOLDER}: Add remaining entries from notebook
138
+ ]
139
+ =======
140
+ data_list = [
141
+ {{'id': 1, 'name': 'Item A'}},
142
+ {{'id': 2, 'name': 'Item B'}},
143
+ {{'id': 3, 'name': 'Item C'}},
144
+ {{'id': 4, 'name': 'Item D'}}
145
+ ]
146
+ <<<<<<< REPLACE
147
+ ```
148
+ </Example 6>
149
+
150
+ <Example 7: Tab structure changes>
118
151
 
119
- Assume the original file has tabs starting at line 10:
120
- ```python
152
+ ```search_replace
153
+ >>>>>>> SEARCH
121
154
  tab1, tab2 = st.tabs(["Cat", "Dog"])
122
155
 
123
156
  with tab1:
@@ -126,57 +159,14 @@ with tab1:
126
159
  with tab2:
127
160
  st.header("A dog")
128
161
  st.image("https://static.streamlit.io/examples/dog.jpg", width=200)
129
- ```
130
-
131
- To consolidate into a single screen without tabs:
132
- ```unified_diff
133
- --- a/app.py
134
- +++ b/app.py
135
- @@ -10,1 +10,1 @@
136
- -tab1, tab2 = st.tabs(["Cat", "Dog"])
137
- -
138
- -with tab1:
139
- - st.header("A cat")
140
- - st.image("https://static.streamlit.io/examples/cat.jpg", width=200)
141
- -with tab2:
142
- - st.header("A dog")
143
- - st.image("https://static.streamlit.io/examples/dog.jpg", width=200)
144
- +st.header("A cat")
145
- +st.image("https://static.streamlit.io/examples/cat.jpg", width=200)
146
- +st.header("A dog")
147
- +st.image("https://static.streamlit.io/examples/dog.jpg", width=200)
148
- ```
149
- </Example 4>
150
-
151
- <Example 5: Adding tab structure - indenting existing content>
152
-
153
- Assume the original file has content starting at line 10:
154
- ```python
162
+ =======
155
163
  st.header("A cat")
156
164
  st.image("https://static.streamlit.io/examples/cat.jpg", width=200)
157
165
  st.header("A dog")
158
166
  st.image("https://static.streamlit.io/examples/dog.jpg", width=200)
167
+ <<<<<<< REPLACE
159
168
  ```
169
+ </Example 7>
160
170
 
161
- To add tab structure:
162
- ```unified_diff
163
- --- a/app.py
164
- +++ b/app.py
165
- @@ -10,1 +10,1 @@
166
- -st.header("A cat")
167
- -st.image("https://static.streamlit.io/examples/cat.jpg", width=200)
168
- -st.header("A dog")
169
- -st.image("https://static.streamlit.io/examples/dog.jpg", width=200)
170
- +tab1, tab2 = st.tabs(["Cat", "Dog"])
171
- +
172
- +with tab1:
173
- + st.header("A cat")
174
- + st.image("https://static.streamlit.io/examples/cat.jpg", width=200)
175
- +with tab2:
176
- + st.header("A dog")
177
- + st.image("https://static.streamlit.io/examples/dog.jpg", width=200)
178
- ```
179
- </Example 5>
180
-
181
- Your response must consist **only** of valid unified-diff block.
171
+ Your response must consist **only** of valid search_replace blocks.
182
172
  """