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.
Files changed (88) hide show
  1. agent_server/core/embedding_service.py +67 -46
  2. agent_server/core/rag_manager.py +31 -17
  3. agent_server/core/reflection_engine.py +0 -1
  4. agent_server/core/retriever.py +13 -8
  5. agent_server/core/vllm_embedding_service.py +243 -0
  6. agent_server/knowledge/watchdog_service.py +1 -1
  7. agent_server/langchain/ARCHITECTURE.md +1193 -0
  8. agent_server/langchain/agent.py +82 -588
  9. agent_server/langchain/custom_middleware.py +663 -0
  10. agent_server/langchain/executors/__init__.py +2 -7
  11. agent_server/langchain/executors/notebook_searcher.py +46 -38
  12. agent_server/langchain/hitl_config.py +71 -0
  13. agent_server/langchain/llm_factory.py +166 -0
  14. agent_server/langchain/logging_utils.py +223 -0
  15. agent_server/langchain/prompts.py +150 -0
  16. agent_server/langchain/state.py +16 -6
  17. agent_server/langchain/tools/__init__.py +19 -0
  18. agent_server/langchain/tools/file_tools.py +354 -114
  19. agent_server/langchain/tools/file_utils.py +334 -0
  20. agent_server/langchain/tools/jupyter_tools.py +18 -18
  21. agent_server/langchain/tools/lsp_tools.py +264 -0
  22. agent_server/langchain/tools/resource_tools.py +161 -0
  23. agent_server/langchain/tools/search_tools.py +198 -216
  24. agent_server/langchain/tools/shell_tools.py +54 -0
  25. agent_server/main.py +11 -1
  26. agent_server/routers/health.py +1 -1
  27. agent_server/routers/langchain_agent.py +1040 -289
  28. agent_server/routers/rag.py +8 -3
  29. hdsp_agent_core/models/rag.py +15 -1
  30. hdsp_agent_core/prompts/auto_agent_prompts.py +3 -3
  31. hdsp_agent_core/services/rag_service.py +6 -1
  32. {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
  33. {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/package.json +3 -2
  34. 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
  35. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.8740a527757068814573.js.map +1 -0
  36. 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
  37. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.e4ff4b5779b5e049f84c.js.map +1 -0
  38. 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
  39. hdsp_jupyter_extension-2.0.8.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.020cdb0b864cfaa4e41e.js.map +1 -0
  40. {hdsp_jupyter_extension-2.0.6.dist-info → hdsp_jupyter_extension-2.0.8.dist-info}/METADATA +2 -1
  41. {hdsp_jupyter_extension-2.0.6.dist-info → hdsp_jupyter_extension-2.0.8.dist-info}/RECORD +75 -69
  42. jupyter_ext/__init__.py +18 -0
  43. jupyter_ext/_version.py +1 -1
  44. jupyter_ext/handlers.py +1351 -58
  45. jupyter_ext/labextension/build_log.json +1 -1
  46. jupyter_ext/labextension/package.json +3 -2
  47. jupyter_ext/labextension/static/{frontend_styles_index_js.02d346171474a0fb2dc1.js → frontend_styles_index_js.8740a527757068814573.js} +470 -7
  48. jupyter_ext/labextension/static/frontend_styles_index_js.8740a527757068814573.js.map +1 -0
  49. jupyter_ext/labextension/static/{lib_index_js.a223ea20056954479ae9.js → lib_index_js.e4ff4b5779b5e049f84c.js} +3196 -441
  50. jupyter_ext/labextension/static/lib_index_js.e4ff4b5779b5e049f84c.js.map +1 -0
  51. jupyter_ext/labextension/static/{remoteEntry.addf2fa038fa60304aa2.js → remoteEntry.020cdb0b864cfaa4e41e.js} +9 -7
  52. jupyter_ext/labextension/static/remoteEntry.020cdb0b864cfaa4e41e.js.map +1 -0
  53. jupyter_ext/resource_usage.py +180 -0
  54. jupyter_ext/tests/test_handlers.py +58 -0
  55. agent_server/langchain/executors/jupyter_executor.py +0 -429
  56. agent_server/langchain/middleware/__init__.py +0 -36
  57. agent_server/langchain/middleware/code_search_middleware.py +0 -278
  58. agent_server/langchain/middleware/error_handling_middleware.py +0 -338
  59. agent_server/langchain/middleware/jupyter_execution_middleware.py +0 -301
  60. agent_server/langchain/middleware/rag_middleware.py +0 -227
  61. agent_server/langchain/middleware/validation_middleware.py +0 -240
  62. hdsp_jupyter_extension-2.0.6.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.02d346171474a0fb2dc1.js.map +0 -1
  63. hdsp_jupyter_extension-2.0.6.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.a223ea20056954479ae9.js.map +0 -1
  64. hdsp_jupyter_extension-2.0.6.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.addf2fa038fa60304aa2.js.map +0 -1
  65. jupyter_ext/labextension/static/frontend_styles_index_js.02d346171474a0fb2dc1.js.map +0 -1
  66. jupyter_ext/labextension/static/lib_index_js.a223ea20056954479ae9.js.map +0 -1
  67. jupyter_ext/labextension/static/remoteEntry.addf2fa038fa60304aa2.js.map +0 -1
  68. {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
  69. {hdsp_jupyter_extension-2.0.6.data → hdsp_jupyter_extension-2.0.8.data}/data/share/jupyter/labextensions/hdsp-agent/install.json +0 -0
  70. {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
  71. {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
  72. {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
  73. {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
  74. {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
  75. {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
  76. {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
  77. {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
  78. {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
  79. {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
  80. {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
  81. {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
  82. {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
  83. {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
  84. {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
  85. {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
  86. {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
  87. {hdsp_jupyter_extension-2.0.6.dist-info → hdsp_jupyter_extension-2.0.8.dist-info}/WHEEL +0 -0
  88. {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