hdsp-jupyter-extension 2.0.0__py3-none-any.whl → 2.0.2__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 (79) hide show
  1. agent_server/langchain/__init__.py +18 -0
  2. agent_server/langchain/agent.py +694 -0
  3. agent_server/langchain/executors/__init__.py +15 -0
  4. agent_server/langchain/executors/jupyter_executor.py +429 -0
  5. agent_server/langchain/executors/notebook_searcher.py +477 -0
  6. agent_server/langchain/middleware/__init__.py +36 -0
  7. agent_server/langchain/middleware/code_search_middleware.py +278 -0
  8. agent_server/langchain/middleware/error_handling_middleware.py +338 -0
  9. agent_server/langchain/middleware/jupyter_execution_middleware.py +301 -0
  10. agent_server/langchain/middleware/rag_middleware.py +227 -0
  11. agent_server/langchain/middleware/validation_middleware.py +240 -0
  12. agent_server/langchain/state.py +159 -0
  13. agent_server/langchain/tools/__init__.py +39 -0
  14. agent_server/langchain/tools/file_tools.py +279 -0
  15. agent_server/langchain/tools/jupyter_tools.py +143 -0
  16. agent_server/langchain/tools/search_tools.py +309 -0
  17. agent_server/main.py +13 -0
  18. agent_server/routers/health.py +14 -0
  19. agent_server/routers/langchain_agent.py +1368 -0
  20. {hdsp_jupyter_extension-2.0.0.data → hdsp_jupyter_extension-2.0.2.data}/data/share/jupyter/labextensions/hdsp-agent/build_log.json +1 -1
  21. {hdsp_jupyter_extension-2.0.0.data → hdsp_jupyter_extension-2.0.2.data}/data/share/jupyter/labextensions/hdsp-agent/package.json +2 -2
  22. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2607ff74c74acfa83158.js → hdsp_jupyter_extension-2.0.2.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.634cf0ae0f3592d0882f.js +408 -4
  23. hdsp_jupyter_extension-2.0.2.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.634cf0ae0f3592d0882f.js.map +1 -0
  24. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.622c1a5918b3aafb2315.js → hdsp_jupyter_extension-2.0.2.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.1366019c413f1d68467f.js +753 -65
  25. hdsp_jupyter_extension-2.0.2.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.1366019c413f1d68467f.js.map +1 -0
  26. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.dae97cde171e13b8c834.js → hdsp_jupyter_extension-2.0.2.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.3379c4b222c042de2b01.js +11 -11
  27. hdsp_jupyter_extension-2.0.2.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.3379c4b222c042de2b01.js.map +1 -0
  28. jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js → hdsp_jupyter_extension-2.0.2.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js +2 -209
  29. hdsp_jupyter_extension-2.0.2.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js.map +1 -0
  30. jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js → hdsp_jupyter_extension-2.0.2.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js +209 -2
  31. hdsp_jupyter_extension-2.0.2.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js.map +1 -0
  32. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js → hdsp_jupyter_extension-2.0.2.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js +212 -3
  33. hdsp_jupyter_extension-2.0.2.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js.map +1 -0
  34. {hdsp_jupyter_extension-2.0.0.dist-info → hdsp_jupyter_extension-2.0.2.dist-info}/METADATA +2 -6
  35. {hdsp_jupyter_extension-2.0.0.dist-info → hdsp_jupyter_extension-2.0.2.dist-info}/RECORD +67 -50
  36. jupyter_ext/__init__.py +1 -1
  37. jupyter_ext/_version.py +1 -1
  38. jupyter_ext/handlers.py +126 -1
  39. jupyter_ext/labextension/build_log.json +1 -1
  40. jupyter_ext/labextension/package.json +2 -2
  41. jupyter_ext/labextension/static/{frontend_styles_index_js.2607ff74c74acfa83158.js → frontend_styles_index_js.634cf0ae0f3592d0882f.js} +408 -4
  42. jupyter_ext/labextension/static/frontend_styles_index_js.634cf0ae0f3592d0882f.js.map +1 -0
  43. jupyter_ext/labextension/static/{lib_index_js.622c1a5918b3aafb2315.js → lib_index_js.1366019c413f1d68467f.js} +753 -65
  44. jupyter_ext/labextension/static/lib_index_js.1366019c413f1d68467f.js.map +1 -0
  45. jupyter_ext/labextension/static/{remoteEntry.dae97cde171e13b8c834.js → remoteEntry.3379c4b222c042de2b01.js} +11 -11
  46. jupyter_ext/labextension/static/remoteEntry.3379c4b222c042de2b01.js.map +1 -0
  47. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js → jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js +2 -209
  48. jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js.map +1 -0
  49. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js → jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js +209 -2
  50. jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js.map +1 -0
  51. jupyter_ext/labextension/static/{vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js → vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js} +212 -3
  52. jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js.map +1 -0
  53. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2607ff74c74acfa83158.js.map +0 -1
  54. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.622c1a5918b3aafb2315.js.map +0 -1
  55. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.dae97cde171e13b8c834.js.map +0 -1
  56. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js.map +0 -1
  57. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js.map +0 -1
  58. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js.map +0 -1
  59. jupyter_ext/labextension/static/frontend_styles_index_js.2607ff74c74acfa83158.js.map +0 -1
  60. jupyter_ext/labextension/static/lib_index_js.622c1a5918b3aafb2315.js.map +0 -1
  61. jupyter_ext/labextension/static/remoteEntry.dae97cde171e13b8c834.js.map +0 -1
  62. jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js.map +0 -1
  63. jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js.map +0 -1
  64. jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js.map +0 -1
  65. {hdsp_jupyter_extension-2.0.0.data → hdsp_jupyter_extension-2.0.2.data}/data/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +0 -0
  66. {hdsp_jupyter_extension-2.0.0.data → hdsp_jupyter_extension-2.0.2.data}/data/share/jupyter/labextensions/hdsp-agent/install.json +0 -0
  67. {hdsp_jupyter_extension-2.0.0.data → hdsp_jupyter_extension-2.0.2.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
  68. {hdsp_jupyter_extension-2.0.0.data → hdsp_jupyter_extension-2.0.2.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
  69. {hdsp_jupyter_extension-2.0.0.data → hdsp_jupyter_extension-2.0.2.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
  70. {hdsp_jupyter_extension-2.0.0.data → hdsp_jupyter_extension-2.0.2.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
  71. {hdsp_jupyter_extension-2.0.0.data → hdsp_jupyter_extension-2.0.2.data}/data/share/jupyter/labextensions/hdsp-agent/static/style.js +0 -0
  72. {hdsp_jupyter_extension-2.0.0.data → hdsp_jupyter_extension-2.0.2.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
  73. {hdsp_jupyter_extension-2.0.0.data → hdsp_jupyter_extension-2.0.2.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
  74. {hdsp_jupyter_extension-2.0.0.data → hdsp_jupyter_extension-2.0.2.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js +0 -0
  75. {hdsp_jupyter_extension-2.0.0.data → hdsp_jupyter_extension-2.0.2.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
  76. {hdsp_jupyter_extension-2.0.0.data → hdsp_jupyter_extension-2.0.2.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +0 -0
  77. {hdsp_jupyter_extension-2.0.0.data → hdsp_jupyter_extension-2.0.2.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js.map +0 -0
  78. {hdsp_jupyter_extension-2.0.0.dist-info → hdsp_jupyter_extension-2.0.2.dist-info}/WHEEL +0 -0
  79. {hdsp_jupyter_extension-2.0.0.dist-info → hdsp_jupyter_extension-2.0.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,279 @@
1
+ """
2
+ File Tools for LangChain Agent
3
+
4
+ Provides tools for file system operations:
5
+ - read_file: Read file content
6
+ - write_file: Write content to file (requires approval)
7
+ - list_files: List directory contents
8
+ """
9
+
10
+ import os
11
+ from typing import Any, Dict, List, Optional
12
+
13
+ from langchain_core.tools import tool
14
+ from pydantic import BaseModel, Field
15
+
16
+
17
+ class ReadFileInput(BaseModel):
18
+ """Input schema for read_file tool"""
19
+ path: str = Field(description="Relative path to the file to read")
20
+ encoding: str = Field(default="utf-8", description="File encoding")
21
+
22
+
23
+ class WriteFileInput(BaseModel):
24
+ """Input schema for write_file tool"""
25
+ path: str = Field(description="Relative path to the file to write")
26
+ content: str = Field(description="Content to write to the file")
27
+ encoding: str = Field(default="utf-8", description="File encoding")
28
+
29
+
30
+ class ListFilesInput(BaseModel):
31
+ """Input schema for list_files tool"""
32
+ path: str = Field(default=".", description="Directory path to list")
33
+ recursive: bool = Field(default=False, description="Whether to list recursively")
34
+ pattern: Optional[str] = Field(
35
+ default=None,
36
+ description="Glob pattern to filter files (e.g., '*.py', '*.ipynb')"
37
+ )
38
+
39
+
40
+ def _validate_path(path: str, workspace_root: str = ".") -> str:
41
+ """
42
+ Validate and resolve file path.
43
+
44
+ Security checks:
45
+ - No absolute paths allowed
46
+ - No parent directory traversal (..)
47
+ - Must be within workspace root
48
+ """
49
+ # Block absolute paths
50
+ if os.path.isabs(path):
51
+ raise ValueError(f"Absolute paths not allowed: {path}")
52
+
53
+ # Block parent directory traversal
54
+ if ".." in path:
55
+ raise ValueError(f"Parent directory traversal not allowed: {path}")
56
+
57
+ # Resolve to absolute path within workspace
58
+ normalized_path = path or "."
59
+ resolved_abs = os.path.abspath(os.path.join(workspace_root, normalized_path))
60
+ workspace_abs = os.path.abspath(workspace_root)
61
+
62
+ # Ensure resolved path is within workspace
63
+ if os.path.commonpath([workspace_abs, resolved_abs]) != workspace_abs:
64
+ raise ValueError(f"Path escapes workspace: {path}")
65
+
66
+ return resolved_abs
67
+
68
+
69
+ @tool(args_schema=ReadFileInput)
70
+ def read_file_tool(
71
+ path: str,
72
+ encoding: str = "utf-8",
73
+ workspace_root: str = "."
74
+ ) -> Dict[str, Any]:
75
+ """
76
+ Read content from a file.
77
+
78
+ Only relative paths within the workspace are allowed.
79
+ Absolute paths and parent directory traversal (..) are blocked.
80
+
81
+ Args:
82
+ path: Relative path to the file
83
+ encoding: File encoding (default: utf-8)
84
+
85
+ Returns:
86
+ Dict with file content or error
87
+ """
88
+ try:
89
+ resolved_path = _validate_path(path, workspace_root)
90
+
91
+ if not os.path.exists(resolved_path):
92
+ return {
93
+ "tool": "read_file",
94
+ "success": False,
95
+ "error": f"File not found: {path}",
96
+ "path": path,
97
+ }
98
+
99
+ if not os.path.isfile(resolved_path):
100
+ return {
101
+ "tool": "read_file",
102
+ "success": False,
103
+ "error": f"Not a file: {path}",
104
+ "path": path,
105
+ }
106
+
107
+ with open(resolved_path, "r", encoding=encoding) as f:
108
+ content = f.read()
109
+
110
+ return {
111
+ "tool": "read_file",
112
+ "success": True,
113
+ "path": path,
114
+ "content": content,
115
+ "size": len(content),
116
+ }
117
+
118
+ except ValueError as e:
119
+ return {
120
+ "tool": "read_file",
121
+ "success": False,
122
+ "error": str(e),
123
+ "path": path,
124
+ }
125
+ except Exception as e:
126
+ return {
127
+ "tool": "read_file",
128
+ "success": False,
129
+ "error": f"Failed to read file: {str(e)}",
130
+ "path": path,
131
+ }
132
+
133
+
134
+ @tool(args_schema=WriteFileInput)
135
+ def write_file_tool(
136
+ path: str,
137
+ content: str,
138
+ encoding: str = "utf-8",
139
+ workspace_root: str = "."
140
+ ) -> Dict[str, Any]:
141
+ """
142
+ Write content to a file.
143
+
144
+ This operation requires user approval before execution.
145
+ Only relative paths within the workspace are allowed.
146
+
147
+ Args:
148
+ path: Relative path to the file
149
+ content: Content to write
150
+ encoding: File encoding (default: utf-8)
151
+
152
+ Returns:
153
+ Dict with operation status (pending approval)
154
+ """
155
+ try:
156
+ resolved_path = _validate_path(path, workspace_root)
157
+
158
+ return {
159
+ "tool": "write_file",
160
+ "status": "pending_approval",
161
+ "path": path,
162
+ "resolved_path": resolved_path,
163
+ "content_preview": content[:200] + "..." if len(content) > 200 else content,
164
+ "content_length": len(content),
165
+ "message": "File write operation requires user approval",
166
+ }
167
+
168
+ except ValueError as e:
169
+ return {
170
+ "tool": "write_file",
171
+ "success": False,
172
+ "error": str(e),
173
+ "path": path,
174
+ }
175
+
176
+
177
+ @tool(args_schema=ListFilesInput)
178
+ def list_files_tool(
179
+ path: str = ".",
180
+ recursive: bool = False,
181
+ pattern: Optional[str] = None,
182
+ workspace_root: str = "."
183
+ ) -> Dict[str, Any]:
184
+ """
185
+ List files and directories.
186
+
187
+ Args:
188
+ path: Directory path to list (default: current directory)
189
+ recursive: Whether to list recursively
190
+ pattern: Optional glob pattern to filter (e.g., '*.py')
191
+
192
+ Returns:
193
+ Dict with list of files and directories
194
+ """
195
+ import fnmatch
196
+
197
+ try:
198
+ resolved_path = _validate_path(path, workspace_root)
199
+
200
+ if not os.path.exists(resolved_path):
201
+ return {
202
+ "tool": "list_files",
203
+ "success": False,
204
+ "error": f"Directory not found: {path}",
205
+ "path": path,
206
+ }
207
+
208
+ if not os.path.isdir(resolved_path):
209
+ return {
210
+ "tool": "list_files",
211
+ "success": False,
212
+ "error": f"Not a directory: {path}",
213
+ "path": path,
214
+ }
215
+
216
+ files: List[Dict[str, Any]] = []
217
+ dirs: List[str] = []
218
+
219
+ if recursive:
220
+ for root, dirnames, filenames in os.walk(resolved_path):
221
+ rel_root = os.path.relpath(root, resolved_path)
222
+ for dirname in dirnames:
223
+ dir_path = os.path.join(rel_root, dirname) if rel_root != "." else dirname
224
+ dirs.append(dir_path)
225
+ for filename in filenames:
226
+ if pattern and not fnmatch.fnmatch(filename, pattern):
227
+ continue
228
+ file_path = os.path.join(rel_root, filename) if rel_root != "." else filename
229
+ full_path = os.path.join(root, filename)
230
+ files.append({
231
+ "name": filename,
232
+ "path": file_path,
233
+ "size": os.path.getsize(full_path),
234
+ })
235
+ else:
236
+ for entry in os.scandir(resolved_path):
237
+ if entry.is_dir():
238
+ dirs.append(entry.name)
239
+ elif entry.is_file():
240
+ if pattern and not fnmatch.fnmatch(entry.name, pattern):
241
+ continue
242
+ files.append({
243
+ "name": entry.name,
244
+ "path": entry.name,
245
+ "size": entry.stat().st_size,
246
+ })
247
+
248
+ return {
249
+ "tool": "list_files",
250
+ "success": True,
251
+ "path": path,
252
+ "directories": sorted(dirs),
253
+ "files": sorted(files, key=lambda x: x["name"]),
254
+ "total_dirs": len(dirs),
255
+ "total_files": len(files),
256
+ }
257
+
258
+ except ValueError as e:
259
+ return {
260
+ "tool": "list_files",
261
+ "success": False,
262
+ "error": str(e),
263
+ "path": path,
264
+ }
265
+ except Exception as e:
266
+ return {
267
+ "tool": "list_files",
268
+ "success": False,
269
+ "error": f"Failed to list directory: {str(e)}",
270
+ "path": path,
271
+ }
272
+
273
+
274
+ # Export all tools
275
+ FILE_TOOLS = [
276
+ read_file_tool,
277
+ write_file_tool,
278
+ list_files_tool,
279
+ ]
@@ -0,0 +1,143 @@
1
+ """
2
+ Jupyter Tools for LangChain Agent
3
+
4
+ Provides tools for interacting with Jupyter notebooks:
5
+ - jupyter_cell: Execute Python code in a new cell
6
+ - markdown: Add a markdown cell
7
+ - final_answer: Complete the task with a summary
8
+ """
9
+
10
+ from typing import Any, Dict, Optional
11
+
12
+ from langchain_core.tools import tool
13
+ from pydantic import BaseModel, Field
14
+
15
+
16
+ class JupyterCellInput(BaseModel):
17
+ """Input schema for jupyter_cell tool"""
18
+ code: str = Field(description="Python code to execute in the notebook cell")
19
+ description: Optional[str] = Field(
20
+ default=None,
21
+ description="Optional description of what this code does"
22
+ )
23
+ execution_result: Optional[Dict[str, Any]] = Field(
24
+ default=None,
25
+ description="Optional execution result payload from the client"
26
+ )
27
+
28
+
29
+ class MarkdownInput(BaseModel):
30
+ """Input schema for markdown tool"""
31
+ content: str = Field(description="Markdown content to add to the notebook")
32
+
33
+
34
+ class FinalAnswerInput(BaseModel):
35
+ """Input schema for final_answer tool"""
36
+ answer: str = Field(description="Final answer/summary to present to the user")
37
+ summary: Optional[str] = Field(
38
+ default=None,
39
+ description="Optional brief summary of what was accomplished"
40
+ )
41
+
42
+
43
+ @tool(args_schema=JupyterCellInput)
44
+ def jupyter_cell_tool(
45
+ code: str,
46
+ description: Optional[str] = None,
47
+ execution_result: Optional[Dict[str, Any]] = None,
48
+ ) -> Dict[str, Any]:
49
+ """
50
+ Execute Python code in a new Jupyter notebook cell.
51
+
52
+ This tool adds a new code cell at the end of the notebook and executes it.
53
+ The execution is handled by JupyterExecutionMiddleware.
54
+
55
+ Args:
56
+ code: Python code to execute
57
+ description: Optional description of the code's purpose
58
+
59
+ Returns:
60
+ Dict containing execution request (actual execution by middleware)
61
+ """
62
+ # Clean code: remove markdown code block wrappers if present
63
+ cleaned_code = code.strip()
64
+ if cleaned_code.startswith("```python"):
65
+ cleaned_code = cleaned_code[9:]
66
+ elif cleaned_code.startswith("```"):
67
+ cleaned_code = cleaned_code[3:]
68
+ if cleaned_code.endswith("```"):
69
+ cleaned_code = cleaned_code[:-3]
70
+ cleaned_code = cleaned_code.strip()
71
+
72
+ response: Dict[str, Any] = {
73
+ "tool": "jupyter_cell",
74
+ "parameters": {
75
+ "code": cleaned_code,
76
+ "description": description,
77
+ },
78
+ "status": "pending_execution",
79
+ "message": "Code cell queued for execution by JupyterExecutionMiddleware"
80
+ }
81
+ if execution_result is not None:
82
+ response["execution_result"] = execution_result
83
+ response["status"] = "complete"
84
+ response["message"] = "Code cell executed with client-reported results"
85
+ return response
86
+
87
+
88
+ @tool(args_schema=MarkdownInput)
89
+ def markdown_tool(content: str) -> Dict[str, Any]:
90
+ """
91
+ Add a markdown cell to the Jupyter notebook.
92
+
93
+ This tool adds a new markdown cell at the end of the notebook.
94
+ Useful for adding explanations, documentation, or section headers.
95
+
96
+ Args:
97
+ content: Markdown content to add
98
+
99
+ Returns:
100
+ Dict containing the markdown addition request
101
+ """
102
+ return {
103
+ "tool": "markdown",
104
+ "parameters": {
105
+ "content": content,
106
+ },
107
+ "status": "completed",
108
+ "message": "Markdown cell added successfully. Continue with the next task."
109
+ }
110
+
111
+
112
+ @tool(args_schema=FinalAnswerInput)
113
+ def final_answer_tool(answer: str, summary: Optional[str] = None) -> Dict[str, Any]:
114
+ """
115
+ Complete the task and provide final answer to the user.
116
+
117
+ Use this tool when you have successfully completed the user's request.
118
+ Provide a clear summary of what was accomplished.
119
+
120
+ Args:
121
+ answer: Final answer/message to the user
122
+ summary: Optional brief summary
123
+
124
+ Returns:
125
+ Dict marking task completion
126
+ """
127
+ return {
128
+ "tool": "final_answer",
129
+ "parameters": {
130
+ "answer": answer,
131
+ "summary": summary,
132
+ },
133
+ "status": "complete",
134
+ "message": "Task completed successfully"
135
+ }
136
+
137
+
138
+ # Export all tools
139
+ JUPYTER_TOOLS = [
140
+ jupyter_cell_tool,
141
+ markdown_tool,
142
+ final_answer_tool,
143
+ ]