hdsp-jupyter-extension 2.0.1__py3-none-any.whl → 2.0.3__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/langchain/__init__.py +18 -0
- agent_server/langchain/agent.py +694 -0
- agent_server/langchain/executors/__init__.py +15 -0
- agent_server/langchain/executors/jupyter_executor.py +429 -0
- agent_server/langchain/executors/notebook_searcher.py +477 -0
- agent_server/langchain/middleware/__init__.py +36 -0
- agent_server/langchain/middleware/code_search_middleware.py +278 -0
- agent_server/langchain/middleware/error_handling_middleware.py +338 -0
- agent_server/langchain/middleware/jupyter_execution_middleware.py +301 -0
- agent_server/langchain/middleware/rag_middleware.py +227 -0
- agent_server/langchain/middleware/validation_middleware.py +240 -0
- agent_server/langchain/state.py +159 -0
- agent_server/langchain/tools/__init__.py +39 -0
- agent_server/langchain/tools/file_tools.py +279 -0
- agent_server/langchain/tools/jupyter_tools.py +143 -0
- agent_server/langchain/tools/search_tools.py +309 -0
- agent_server/main.py +13 -0
- agent_server/routers/health.py +14 -0
- agent_server/routers/langchain_agent.py +1368 -0
- {hdsp_jupyter_extension-2.0.1.data → hdsp_jupyter_extension-2.0.3.data}/data/share/jupyter/labextensions/hdsp-agent/build_log.json +1 -1
- {hdsp_jupyter_extension-2.0.1.data → hdsp_jupyter_extension-2.0.3.data}/data/share/jupyter/labextensions/hdsp-agent/package.json +2 -2
- hdsp_jupyter_extension-2.0.1.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2607ff74c74acfa83158.js → hdsp_jupyter_extension-2.0.3.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.634cf0ae0f3592d0882f.js +408 -4
- hdsp_jupyter_extension-2.0.3.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.634cf0ae0f3592d0882f.js.map +1 -0
- hdsp_jupyter_extension-2.0.1.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.622c1a5918b3aafb2315.js → hdsp_jupyter_extension-2.0.3.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.1366019c413f1d68467f.js +753 -65
- hdsp_jupyter_extension-2.0.3.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.1366019c413f1d68467f.js.map +1 -0
- hdsp_jupyter_extension-2.0.1.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.729f933de01ad5620730.js → hdsp_jupyter_extension-2.0.3.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.b6d91b150c0800bddfa4.js +8 -8
- hdsp_jupyter_extension-2.0.3.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.b6d91b150c0800bddfa4.js.map +1 -0
- 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.3.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js +2 -209
- hdsp_jupyter_extension-2.0.3.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
- jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js → hdsp_jupyter_extension-2.0.3.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js +209 -2
- hdsp_jupyter_extension-2.0.3.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
- hdsp_jupyter_extension-2.0.1.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js → hdsp_jupyter_extension-2.0.3.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js +212 -3
- hdsp_jupyter_extension-2.0.3.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js.map +1 -0
- {hdsp_jupyter_extension-2.0.1.dist-info → hdsp_jupyter_extension-2.0.3.dist-info}/METADATA +6 -1
- {hdsp_jupyter_extension-2.0.1.dist-info → hdsp_jupyter_extension-2.0.3.dist-info}/RECORD +66 -49
- jupyter_ext/_version.py +1 -1
- jupyter_ext/handlers.py +126 -1
- jupyter_ext/labextension/build_log.json +1 -1
- jupyter_ext/labextension/package.json +2 -2
- jupyter_ext/labextension/static/{frontend_styles_index_js.2607ff74c74acfa83158.js → frontend_styles_index_js.634cf0ae0f3592d0882f.js} +408 -4
- jupyter_ext/labextension/static/frontend_styles_index_js.634cf0ae0f3592d0882f.js.map +1 -0
- jupyter_ext/labextension/static/{lib_index_js.622c1a5918b3aafb2315.js → lib_index_js.1366019c413f1d68467f.js} +753 -65
- jupyter_ext/labextension/static/lib_index_js.1366019c413f1d68467f.js.map +1 -0
- jupyter_ext/labextension/static/{remoteEntry.729f933de01ad5620730.js → remoteEntry.b6d91b150c0800bddfa4.js} +8 -8
- jupyter_ext/labextension/static/remoteEntry.b6d91b150c0800bddfa4.js.map +1 -0
- hdsp_jupyter_extension-2.0.1.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
- jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js.map +1 -0
- hdsp_jupyter_extension-2.0.1.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
- jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js.map +1 -0
- 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
- jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js.map +1 -0
- hdsp_jupyter_extension-2.0.1.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2607ff74c74acfa83158.js.map +0 -1
- hdsp_jupyter_extension-2.0.1.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.622c1a5918b3aafb2315.js.map +0 -1
- hdsp_jupyter_extension-2.0.1.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.729f933de01ad5620730.js.map +0 -1
- hdsp_jupyter_extension-2.0.1.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
- hdsp_jupyter_extension-2.0.1.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
- hdsp_jupyter_extension-2.0.1.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js.map +0 -1
- jupyter_ext/labextension/static/frontend_styles_index_js.2607ff74c74acfa83158.js.map +0 -1
- jupyter_ext/labextension/static/lib_index_js.622c1a5918b3aafb2315.js.map +0 -1
- jupyter_ext/labextension/static/remoteEntry.729f933de01ad5620730.js.map +0 -1
- jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js.map +0 -1
- jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js.map +0 -1
- jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js.map +0 -1
- {hdsp_jupyter_extension-2.0.1.data → hdsp_jupyter_extension-2.0.3.data}/data/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +0 -0
- {hdsp_jupyter_extension-2.0.1.data → hdsp_jupyter_extension-2.0.3.data}/data/share/jupyter/labextensions/hdsp-agent/install.json +0 -0
- {hdsp_jupyter_extension-2.0.1.data → hdsp_jupyter_extension-2.0.3.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.1.data → hdsp_jupyter_extension-2.0.3.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.1.data → hdsp_jupyter_extension-2.0.3.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.1.data → hdsp_jupyter_extension-2.0.3.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.1.data → hdsp_jupyter_extension-2.0.3.data}/data/share/jupyter/labextensions/hdsp-agent/static/style.js +0 -0
- {hdsp_jupyter_extension-2.0.1.data → hdsp_jupyter_extension-2.0.3.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.1.data → hdsp_jupyter_extension-2.0.3.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.1.data → hdsp_jupyter_extension-2.0.3.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.1.data → hdsp_jupyter_extension-2.0.3.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.1.data → hdsp_jupyter_extension-2.0.3.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +0 -0
- {hdsp_jupyter_extension-2.0.1.data → hdsp_jupyter_extension-2.0.3.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.1.dist-info → hdsp_jupyter_extension-2.0.3.dist-info}/WHEEL +0 -0
- {hdsp_jupyter_extension-2.0.1.dist-info → hdsp_jupyter_extension-2.0.3.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
|
+
]
|