hdsp-jupyter-extension 2.0.6__py3-none-any.whl → 2.0.8__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.
- agent_server/core/embedding_service.py +67 -46
- agent_server/core/rag_manager.py +31 -17
- agent_server/core/reflection_engine.py +0 -1
- agent_server/core/retriever.py +13 -8
- agent_server/core/vllm_embedding_service.py +243 -0
- agent_server/knowledge/watchdog_service.py +1 -1
- agent_server/langchain/ARCHITECTURE.md +1193 -0
- agent_server/langchain/agent.py +82 -588
- agent_server/langchain/custom_middleware.py +663 -0
- agent_server/langchain/executors/__init__.py +2 -7
- agent_server/langchain/executors/notebook_searcher.py +46 -38
- agent_server/langchain/hitl_config.py +71 -0
- agent_server/langchain/llm_factory.py +166 -0
- agent_server/langchain/logging_utils.py +223 -0
- agent_server/langchain/prompts.py +150 -0
- agent_server/langchain/state.py +16 -6
- agent_server/langchain/tools/__init__.py +19 -0
- agent_server/langchain/tools/file_tools.py +354 -114
- agent_server/langchain/tools/file_utils.py +334 -0
- agent_server/langchain/tools/jupyter_tools.py +18 -18
- agent_server/langchain/tools/lsp_tools.py +264 -0
- agent_server/langchain/tools/resource_tools.py +161 -0
- agent_server/langchain/tools/search_tools.py +198 -216
- agent_server/langchain/tools/shell_tools.py +54 -0
- agent_server/main.py +11 -1
- agent_server/routers/health.py +1 -1
- agent_server/routers/langchain_agent.py +1040 -289
- agent_server/routers/rag.py +8 -3
- hdsp_agent_core/models/rag.py +15 -1
- hdsp_agent_core/prompts/auto_agent_prompts.py +3 -3
- hdsp_agent_core/services/rag_service.py +6 -1
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/build_log.json +1 -1
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/package.json +3 -2
- hdsp_jupyter_extension-2.0.6.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.02d346171474a0fb2dc1.js → hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.8740a527757068814573.js +470 -7
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.8740a527757068814573.js.map +1 -0
- hdsp_jupyter_extension-2.0.6.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.a223ea20056954479ae9.js → hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.e4ff4b5779b5e049f84c.js +3196 -441
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.e4ff4b5779b5e049f84c.js.map +1 -0
- hdsp_jupyter_extension-2.0.6.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.addf2fa038fa60304aa2.js → hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.020cdb0b864cfaa4e41e.js +9 -7
- hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.020cdb0b864cfaa4e41e.js.map +1 -0
- {hdsp_jupyter_extension-2.0.6.dist-info → hdsp_jupyter_extension-2.0.8.dist-info}/METADATA +2 -1
- {hdsp_jupyter_extension-2.0.6.dist-info → hdsp_jupyter_extension-2.0.8.dist-info}/RECORD +75 -69
- jupyter_ext/__init__.py +18 -0
- jupyter_ext/_version.py +1 -1
- jupyter_ext/handlers.py +1351 -58
- jupyter_ext/labextension/build_log.json +1 -1
- jupyter_ext/labextension/package.json +3 -2
- jupyter_ext/labextension/static/{frontend_styles_index_js.02d346171474a0fb2dc1.js → frontend_styles_index_js.8740a527757068814573.js} +470 -7
- jupyter_ext/labextension/static/frontend_styles_index_js.8740a527757068814573.js.map +1 -0
- jupyter_ext/labextension/static/{lib_index_js.a223ea20056954479ae9.js → lib_index_js.e4ff4b5779b5e049f84c.js} +3196 -441
- jupyter_ext/labextension/static/lib_index_js.e4ff4b5779b5e049f84c.js.map +1 -0
- jupyter_ext/labextension/static/{remoteEntry.addf2fa038fa60304aa2.js → remoteEntry.020cdb0b864cfaa4e41e.js} +9 -7
- jupyter_ext/labextension/static/remoteEntry.020cdb0b864cfaa4e41e.js.map +1 -0
- jupyter_ext/resource_usage.py +180 -0
- jupyter_ext/tests/test_handlers.py +58 -0
- agent_server/langchain/executors/jupyter_executor.py +0 -429
- agent_server/langchain/middleware/__init__.py +0 -36
- agent_server/langchain/middleware/code_search_middleware.py +0 -278
- agent_server/langchain/middleware/error_handling_middleware.py +0 -338
- agent_server/langchain/middleware/jupyter_execution_middleware.py +0 -301
- agent_server/langchain/middleware/rag_middleware.py +0 -227
- agent_server/langchain/middleware/validation_middleware.py +0 -240
- hdsp_jupyter_extension-2.0.6.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.02d346171474a0fb2dc1.js.map +0 -1
- hdsp_jupyter_extension-2.0.6.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.a223ea20056954479ae9.js.map +0 -1
- hdsp_jupyter_extension-2.0.6.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.addf2fa038fa60304aa2.js.map +0 -1
- jupyter_ext/labextension/static/frontend_styles_index_js.02d346171474a0fb2dc1.js.map +0 -1
- jupyter_ext/labextension/static/lib_index_js.a223ea20056954479ae9.js.map +0 -1
- jupyter_ext/labextension/static/remoteEntry.addf2fa038fa60304aa2.js.map +0 -1
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/install.json +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b80.c095373419d05e6f141a.js +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b80.c095373419d05e6f141a.js.map +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b81.61e75fb98ecff46cf836.js +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b81.61e75fb98ecff46cf836.js.map +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/style.js +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195.e2553aab0c3963b83dd7.js +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195.e2553aab0c3963b83dd7.js.map +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js.map +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js.map +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js.map +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js.map +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js +0 -0
- {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js.map +0 -0
- {hdsp_jupyter_extension-2.0.6.dist-info → hdsp_jupyter_extension-2.0.8.dist-info}/WHEEL +0 -0
- {hdsp_jupyter_extension-2.0.6.dist-info → hdsp_jupyter_extension-2.0.8.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
"""
|
|
2
|
+
File Utilities for LangChain Agent
|
|
3
|
+
|
|
4
|
+
Provides utility functions for file operations:
|
|
5
|
+
- perform_string_replacement: String replacement with occurrence validation
|
|
6
|
+
- compute_unified_diff: Generate unified diff between before/after content
|
|
7
|
+
- count_diff_changes: Count additions/deletions from diff
|
|
8
|
+
- format_content_with_line_numbers: Format file content with line numbers (cat -n style)
|
|
9
|
+
- check_empty_content: Check if content is empty and return warning message
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import difflib
|
|
13
|
+
from typing import List, Optional, Tuple, Union
|
|
14
|
+
|
|
15
|
+
# Constants for file reading (aligned with DeepAgents)
|
|
16
|
+
EMPTY_CONTENT_WARNING = "System reminder: File exists but has empty contents"
|
|
17
|
+
MAX_LINE_LENGTH = 10000 # Chunk lines longer than this
|
|
18
|
+
LINE_NUMBER_WIDTH = 6 # Width for line number padding
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def check_empty_content(content: str) -> Optional[str]:
|
|
22
|
+
"""
|
|
23
|
+
Check if content is empty and return warning message.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
content: Content to check
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Warning message if empty, None otherwise
|
|
30
|
+
"""
|
|
31
|
+
if not content or content.strip() == "":
|
|
32
|
+
return EMPTY_CONTENT_WARNING
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def format_content_with_line_numbers(
|
|
37
|
+
content: Union[str, List[str]],
|
|
38
|
+
start_line: int = 1,
|
|
39
|
+
) -> str:
|
|
40
|
+
"""
|
|
41
|
+
Format file content with line numbers (cat -n style).
|
|
42
|
+
|
|
43
|
+
Chunks lines longer than MAX_LINE_LENGTH with continuation markers (e.g., 5.1, 5.2).
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
content: File content as string or list of lines
|
|
47
|
+
start_line: Starting line number (default: 1)
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Formatted content with line numbers and continuation markers
|
|
51
|
+
"""
|
|
52
|
+
if isinstance(content, str):
|
|
53
|
+
lines = content.split("\n")
|
|
54
|
+
# Remove trailing empty line if content ends with newline
|
|
55
|
+
if lines and lines[-1] == "":
|
|
56
|
+
lines = lines[:-1]
|
|
57
|
+
else:
|
|
58
|
+
lines = content
|
|
59
|
+
|
|
60
|
+
result_lines = []
|
|
61
|
+
for i, line in enumerate(lines):
|
|
62
|
+
line_num = i + start_line
|
|
63
|
+
|
|
64
|
+
if len(line) <= MAX_LINE_LENGTH:
|
|
65
|
+
result_lines.append(f"{line_num:{LINE_NUMBER_WIDTH}d}\t{line}")
|
|
66
|
+
else:
|
|
67
|
+
# Split long line into chunks with continuation markers
|
|
68
|
+
num_chunks = (len(line) + MAX_LINE_LENGTH - 1) // MAX_LINE_LENGTH
|
|
69
|
+
for chunk_idx in range(num_chunks):
|
|
70
|
+
start = chunk_idx * MAX_LINE_LENGTH
|
|
71
|
+
end = min(start + MAX_LINE_LENGTH, len(line))
|
|
72
|
+
chunk = line[start:end]
|
|
73
|
+
if chunk_idx == 0:
|
|
74
|
+
# First chunk: use normal line number
|
|
75
|
+
result_lines.append(f"{line_num:{LINE_NUMBER_WIDTH}d}\t{chunk}")
|
|
76
|
+
else:
|
|
77
|
+
# Continuation chunks: use decimal notation (e.g., 5.1, 5.2)
|
|
78
|
+
continuation_marker = f"{line_num}.{chunk_idx}"
|
|
79
|
+
result_lines.append(
|
|
80
|
+
f"{continuation_marker:>{LINE_NUMBER_WIDTH}}\t{chunk}"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return "\n".join(result_lines)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def format_read_response(
|
|
87
|
+
content: str,
|
|
88
|
+
offset: int = 0,
|
|
89
|
+
limit: int = 500,
|
|
90
|
+
) -> str:
|
|
91
|
+
"""
|
|
92
|
+
Format file content for read response with line numbers and pagination.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
content: Full file content
|
|
96
|
+
offset: Line offset (0-indexed)
|
|
97
|
+
limit: Maximum number of lines to return
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Formatted content with line numbers, or error/warning message
|
|
101
|
+
"""
|
|
102
|
+
# Check for empty content
|
|
103
|
+
empty_msg = check_empty_content(content)
|
|
104
|
+
if empty_msg:
|
|
105
|
+
return empty_msg
|
|
106
|
+
|
|
107
|
+
lines = content.splitlines()
|
|
108
|
+
total_lines = len(lines)
|
|
109
|
+
|
|
110
|
+
# Validate offset
|
|
111
|
+
if offset >= total_lines:
|
|
112
|
+
return f"Error: Line offset {offset} exceeds file length ({total_lines} lines)"
|
|
113
|
+
|
|
114
|
+
# Apply pagination
|
|
115
|
+
start_idx = offset
|
|
116
|
+
end_idx = min(start_idx + limit, total_lines)
|
|
117
|
+
selected_lines = lines[start_idx:end_idx]
|
|
118
|
+
|
|
119
|
+
# Format with line numbers
|
|
120
|
+
formatted = format_content_with_line_numbers(
|
|
121
|
+
selected_lines, start_line=start_idx + 1
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# Add pagination info if truncated
|
|
125
|
+
if end_idx < total_lines:
|
|
126
|
+
remaining = total_lines - end_idx
|
|
127
|
+
formatted += f"\n\n[... {remaining} more lines. Use offset={end_idx} to continue reading]"
|
|
128
|
+
|
|
129
|
+
return formatted
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _normalize_whitespace(text: str) -> str:
|
|
133
|
+
"""Normalize line endings and trailing whitespace per line."""
|
|
134
|
+
lines = text.replace("\r\n", "\n").replace("\r", "\n").split("\n")
|
|
135
|
+
return "\n".join(line.rstrip() for line in lines)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def perform_string_replacement(
|
|
139
|
+
content: str,
|
|
140
|
+
old_string: str,
|
|
141
|
+
new_string: str,
|
|
142
|
+
replace_all: bool = False,
|
|
143
|
+
) -> Union[Tuple[str, int], str]:
|
|
144
|
+
"""
|
|
145
|
+
Perform string replacement with occurrence validation.
|
|
146
|
+
|
|
147
|
+
Includes fallback strategies for more robust matching:
|
|
148
|
+
1. Exact match
|
|
149
|
+
2. Strip leading/trailing newlines from old_string
|
|
150
|
+
3. Normalize whitespace (line endings, trailing spaces)
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
content: Original file content
|
|
154
|
+
old_string: String to replace
|
|
155
|
+
new_string: Replacement string
|
|
156
|
+
replace_all: Whether to replace all occurrences
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Tuple of (new_content, occurrences) on success,
|
|
160
|
+
or error message string on failure
|
|
161
|
+
"""
|
|
162
|
+
# Strategy 1: Exact match
|
|
163
|
+
occurrences = content.count(old_string)
|
|
164
|
+
|
|
165
|
+
if occurrences == 0:
|
|
166
|
+
# Strategy 2: Strip leading/trailing newlines from old_string
|
|
167
|
+
stripped_old = old_string.strip("\n")
|
|
168
|
+
occurrences = content.count(stripped_old)
|
|
169
|
+
if occurrences > 0:
|
|
170
|
+
old_string = stripped_old
|
|
171
|
+
# Also strip new_string's leading/trailing newlines to match
|
|
172
|
+
new_string = new_string.strip("\n")
|
|
173
|
+
|
|
174
|
+
if occurrences == 0:
|
|
175
|
+
# Strategy 3: Normalize whitespace (line endings, trailing spaces)
|
|
176
|
+
normalized_content = _normalize_whitespace(content)
|
|
177
|
+
normalized_old = _normalize_whitespace(old_string.strip("\n"))
|
|
178
|
+
occurrences = normalized_content.count(normalized_old)
|
|
179
|
+
|
|
180
|
+
if occurrences > 0:
|
|
181
|
+
# Find the original text in content that matches normalized version
|
|
182
|
+
# We need to do replacement on the normalized content first
|
|
183
|
+
normalized_new = _normalize_whitespace(new_string.strip("\n"))
|
|
184
|
+
if occurrences > 1 and not replace_all:
|
|
185
|
+
preview = (
|
|
186
|
+
old_string[:50] + "..." if len(old_string) > 50 else old_string
|
|
187
|
+
)
|
|
188
|
+
return (
|
|
189
|
+
f"Error: String '{preview}' appears {occurrences} times in file. "
|
|
190
|
+
"Use replace_all=True to replace all instances, "
|
|
191
|
+
"or provide a more specific string with surrounding context."
|
|
192
|
+
)
|
|
193
|
+
# Replace on normalized content, then return
|
|
194
|
+
new_content = normalized_content.replace(normalized_old, normalized_new)
|
|
195
|
+
return new_content, occurrences
|
|
196
|
+
|
|
197
|
+
if occurrences == 0:
|
|
198
|
+
# All strategies failed
|
|
199
|
+
preview = old_string[:100] + "..." if len(old_string) > 100 else old_string
|
|
200
|
+
return f"Error: String not found in file: '{preview}'"
|
|
201
|
+
|
|
202
|
+
if occurrences > 1 and not replace_all:
|
|
203
|
+
preview = old_string[:50] + "..." if len(old_string) > 50 else old_string
|
|
204
|
+
return (
|
|
205
|
+
f"Error: String '{preview}' appears {occurrences} times in file. "
|
|
206
|
+
"Use replace_all=True to replace all instances, "
|
|
207
|
+
"or provide a more specific string with surrounding context."
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
new_content = content.replace(old_string, new_string)
|
|
211
|
+
return new_content, occurrences
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def compute_unified_diff(
|
|
215
|
+
before: str,
|
|
216
|
+
after: str,
|
|
217
|
+
filepath: str,
|
|
218
|
+
max_lines: int = 100,
|
|
219
|
+
context_lines: int = 3,
|
|
220
|
+
) -> Union[str, None]:
|
|
221
|
+
"""
|
|
222
|
+
Compute a unified diff between before and after content.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
before: Original content
|
|
226
|
+
after: New content
|
|
227
|
+
filepath: Path for display in diff headers
|
|
228
|
+
max_lines: Maximum number of diff lines (None for unlimited)
|
|
229
|
+
context_lines: Number of context lines around changes (default 3)
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
Unified diff string or None if no changes
|
|
233
|
+
"""
|
|
234
|
+
before_lines = before.splitlines()
|
|
235
|
+
after_lines = after.splitlines()
|
|
236
|
+
|
|
237
|
+
diff_lines = list(
|
|
238
|
+
difflib.unified_diff(
|
|
239
|
+
before_lines,
|
|
240
|
+
after_lines,
|
|
241
|
+
fromfile=f"{filepath} (before)",
|
|
242
|
+
tofile=f"{filepath} (after)",
|
|
243
|
+
lineterm="",
|
|
244
|
+
n=context_lines,
|
|
245
|
+
)
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
if not diff_lines:
|
|
249
|
+
return None
|
|
250
|
+
|
|
251
|
+
if max_lines and len(diff_lines) > max_lines:
|
|
252
|
+
truncated = diff_lines[: max_lines - 1]
|
|
253
|
+
truncated.append(
|
|
254
|
+
f"... [{len(diff_lines) - max_lines + 1} more lines truncated]"
|
|
255
|
+
)
|
|
256
|
+
return "\n".join(truncated)
|
|
257
|
+
|
|
258
|
+
return "\n".join(diff_lines)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def count_diff_changes(diff: str) -> Tuple[int, int]:
|
|
262
|
+
"""
|
|
263
|
+
Count additions and deletions from unified diff.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
diff: Unified diff string
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
Tuple of (additions, deletions) line counts
|
|
270
|
+
"""
|
|
271
|
+
if not diff:
|
|
272
|
+
return 0, 0
|
|
273
|
+
|
|
274
|
+
additions = sum(
|
|
275
|
+
1
|
|
276
|
+
for line in diff.splitlines()
|
|
277
|
+
if line.startswith("+") and not line.startswith("+++")
|
|
278
|
+
)
|
|
279
|
+
deletions = sum(
|
|
280
|
+
1
|
|
281
|
+
for line in diff.splitlines()
|
|
282
|
+
if line.startswith("-") and not line.startswith("---")
|
|
283
|
+
)
|
|
284
|
+
return additions, deletions
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def build_edit_preview(
|
|
288
|
+
original_content: str,
|
|
289
|
+
old_string: str,
|
|
290
|
+
new_string: str,
|
|
291
|
+
replace_all: bool,
|
|
292
|
+
filepath: str,
|
|
293
|
+
) -> dict:
|
|
294
|
+
"""
|
|
295
|
+
Build a preview for edit_file operation including diff.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
original_content: Current file content
|
|
299
|
+
old_string: String to replace
|
|
300
|
+
new_string: Replacement string
|
|
301
|
+
replace_all: Whether to replace all occurrences
|
|
302
|
+
filepath: File path for display
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
Dict with diff, occurrences, and change counts
|
|
306
|
+
"""
|
|
307
|
+
result = perform_string_replacement(
|
|
308
|
+
original_content, old_string, new_string, replace_all
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
if isinstance(result, str):
|
|
312
|
+
# Error case
|
|
313
|
+
return {
|
|
314
|
+
"success": False,
|
|
315
|
+
"error": result,
|
|
316
|
+
"diff": None,
|
|
317
|
+
"occurrences": 0,
|
|
318
|
+
"lines_added": 0,
|
|
319
|
+
"lines_removed": 0,
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
new_content, occurrences = result
|
|
323
|
+
diff = compute_unified_diff(original_content, new_content, filepath)
|
|
324
|
+
additions, deletions = count_diff_changes(diff) if diff else (0, 0)
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
"success": True,
|
|
328
|
+
"error": None,
|
|
329
|
+
"diff": diff,
|
|
330
|
+
"occurrences": occurrences,
|
|
331
|
+
"lines_added": additions,
|
|
332
|
+
"lines_removed": deletions,
|
|
333
|
+
"new_content": new_content,
|
|
334
|
+
}
|
|
@@ -15,28 +15,28 @@ from pydantic import BaseModel, Field
|
|
|
15
15
|
|
|
16
16
|
class JupyterCellInput(BaseModel):
|
|
17
17
|
"""Input schema for jupyter_cell tool"""
|
|
18
|
+
|
|
18
19
|
code: str = Field(description="Python code to execute in the notebook cell")
|
|
19
20
|
description: Optional[str] = Field(
|
|
20
|
-
default=None,
|
|
21
|
-
description="Optional description of what this code does"
|
|
21
|
+
default=None, description="Optional description of what this code does"
|
|
22
22
|
)
|
|
23
23
|
execution_result: Optional[Dict[str, Any]] = Field(
|
|
24
|
-
default=None,
|
|
25
|
-
description="Optional execution result payload from the client"
|
|
24
|
+
default=None, description="Optional execution result payload from the client"
|
|
26
25
|
)
|
|
27
26
|
|
|
28
27
|
|
|
29
28
|
class MarkdownInput(BaseModel):
|
|
30
29
|
"""Input schema for markdown tool"""
|
|
30
|
+
|
|
31
31
|
content: str = Field(description="Markdown content to add to the notebook")
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
class FinalAnswerInput(BaseModel):
|
|
35
35
|
"""Input schema for final_answer tool"""
|
|
36
|
+
|
|
36
37
|
answer: str = Field(description="Final answer/summary to present to the user")
|
|
37
38
|
summary: Optional[str] = Field(
|
|
38
|
-
default=None,
|
|
39
|
-
description="Optional brief summary of what was accomplished"
|
|
39
|
+
default=None, description="Optional brief summary of what was accomplished"
|
|
40
40
|
)
|
|
41
41
|
|
|
42
42
|
|
|
@@ -48,14 +48,14 @@ def jupyter_cell_tool(
|
|
|
48
48
|
) -> Dict[str, Any]:
|
|
49
49
|
"""
|
|
50
50
|
Execute Python code in a new Jupyter notebook cell.
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
This tool adds a new code cell at the end of the notebook and executes it.
|
|
53
53
|
The execution is handled by JupyterExecutionMiddleware.
|
|
54
|
-
|
|
54
|
+
|
|
55
55
|
Args:
|
|
56
56
|
code: Python code to execute
|
|
57
57
|
description: Optional description of the code's purpose
|
|
58
|
-
|
|
58
|
+
|
|
59
59
|
Returns:
|
|
60
60
|
Dict containing execution request (actual execution by middleware)
|
|
61
61
|
"""
|
|
@@ -76,7 +76,7 @@ def jupyter_cell_tool(
|
|
|
76
76
|
"description": description,
|
|
77
77
|
},
|
|
78
78
|
"status": "pending_execution",
|
|
79
|
-
"message": "Code cell queued for execution by JupyterExecutionMiddleware"
|
|
79
|
+
"message": "Code cell queued for execution by JupyterExecutionMiddleware",
|
|
80
80
|
}
|
|
81
81
|
if execution_result is not None:
|
|
82
82
|
response["execution_result"] = execution_result
|
|
@@ -89,13 +89,13 @@ def jupyter_cell_tool(
|
|
|
89
89
|
def markdown_tool(content: str) -> Dict[str, Any]:
|
|
90
90
|
"""
|
|
91
91
|
Add a markdown cell to the Jupyter notebook.
|
|
92
|
-
|
|
92
|
+
|
|
93
93
|
This tool adds a new markdown cell at the end of the notebook.
|
|
94
94
|
Useful for adding explanations, documentation, or section headers.
|
|
95
|
-
|
|
95
|
+
|
|
96
96
|
Args:
|
|
97
97
|
content: Markdown content to add
|
|
98
|
-
|
|
98
|
+
|
|
99
99
|
Returns:
|
|
100
100
|
Dict containing the markdown addition request
|
|
101
101
|
"""
|
|
@@ -105,7 +105,7 @@ def markdown_tool(content: str) -> Dict[str, Any]:
|
|
|
105
105
|
"content": content,
|
|
106
106
|
},
|
|
107
107
|
"status": "completed",
|
|
108
|
-
"message": "Markdown cell added successfully. Continue with the next task."
|
|
108
|
+
"message": "Markdown cell added successfully. Continue with the next task.",
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
|
|
@@ -113,14 +113,14 @@ def markdown_tool(content: str) -> Dict[str, Any]:
|
|
|
113
113
|
def final_answer_tool(answer: str, summary: Optional[str] = None) -> Dict[str, Any]:
|
|
114
114
|
"""
|
|
115
115
|
Complete the task and provide final answer to the user.
|
|
116
|
-
|
|
116
|
+
|
|
117
117
|
Use this tool when you have successfully completed the user's request.
|
|
118
118
|
Provide a clear summary of what was accomplished.
|
|
119
|
-
|
|
119
|
+
|
|
120
120
|
Args:
|
|
121
121
|
answer: Final answer/message to the user
|
|
122
122
|
summary: Optional brief summary
|
|
123
|
-
|
|
123
|
+
|
|
124
124
|
Returns:
|
|
125
125
|
Dict marking task completion
|
|
126
126
|
"""
|
|
@@ -131,7 +131,7 @@ def final_answer_tool(answer: str, summary: Optional[str] = None) -> Dict[str, A
|
|
|
131
131
|
"summary": summary,
|
|
132
132
|
},
|
|
133
133
|
"status": "complete",
|
|
134
|
-
"message": "Task completed successfully"
|
|
134
|
+
"message": "Task completed successfully",
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
|