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,309 @@
1
+ """
2
+ Search Tools for LangChain Agent
3
+
4
+ Provides tools for searching code in workspace and notebooks:
5
+ - search_workspace: Search files in the workspace
6
+ - search_notebook_cells: Search cells in Jupyter notebooks
7
+ """
8
+
9
+ import json
10
+ import os
11
+ import re
12
+ from typing import Any, Dict, List, Optional
13
+
14
+ from langchain_core.tools import tool
15
+ from pydantic import BaseModel, Field
16
+
17
+
18
+ class SearchWorkspaceInput(BaseModel):
19
+ """Input schema for search_workspace tool"""
20
+ pattern: str = Field(description="Search pattern (regex or text)")
21
+ file_types: List[str] = Field(
22
+ default=["*.py", "*.ipynb"],
23
+ description="File patterns to search (e.g., ['*.py', '*.ipynb'])"
24
+ )
25
+ path: str = Field(default=".", description="Directory to search in")
26
+ max_results: int = Field(default=50, description="Maximum number of results")
27
+ case_sensitive: bool = Field(default=False, description="Case-sensitive search")
28
+
29
+
30
+ class SearchNotebookCellsInput(BaseModel):
31
+ """Input schema for search_notebook_cells tool"""
32
+ pattern: str = Field(description="Search pattern (regex or text)")
33
+ notebook_path: Optional[str] = Field(
34
+ default=None,
35
+ description="Specific notebook to search (None = all notebooks)"
36
+ )
37
+ cell_type: Optional[str] = Field(
38
+ default=None,
39
+ description="Cell type filter: 'code', 'markdown', or None for all"
40
+ )
41
+ max_results: int = Field(default=30, description="Maximum number of results")
42
+ case_sensitive: bool = Field(default=False, description="Case-sensitive search")
43
+
44
+
45
+ def _search_in_file(
46
+ file_path: str,
47
+ pattern: str,
48
+ case_sensitive: bool = False
49
+ ) -> List[Dict[str, Any]]:
50
+ """Search for pattern in a regular file"""
51
+ results = []
52
+ flags = 0 if case_sensitive else re.IGNORECASE
53
+
54
+ try:
55
+ compiled = re.compile(pattern, flags)
56
+ except re.error:
57
+ # If pattern is not valid regex, use literal search
58
+ compiled = re.compile(re.escape(pattern), flags)
59
+
60
+ try:
61
+ with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
62
+ for line_num, line in enumerate(f, 1):
63
+ if compiled.search(line):
64
+ results.append({
65
+ "file_path": file_path,
66
+ "line_number": line_num,
67
+ "content": line.strip()[:200],
68
+ "match_type": "line",
69
+ })
70
+ except Exception:
71
+ pass
72
+
73
+ return results
74
+
75
+
76
+ def _search_in_notebook(
77
+ notebook_path: str,
78
+ pattern: str,
79
+ cell_type: Optional[str] = None,
80
+ case_sensitive: bool = False
81
+ ) -> List[Dict[str, Any]]:
82
+ """Search for pattern in a Jupyter notebook"""
83
+ results = []
84
+ flags = 0 if case_sensitive else re.IGNORECASE
85
+
86
+ try:
87
+ compiled = re.compile(pattern, flags)
88
+ except re.error:
89
+ compiled = re.compile(re.escape(pattern), flags)
90
+
91
+ try:
92
+ with open(notebook_path, "r", encoding="utf-8") as f:
93
+ notebook = json.load(f)
94
+
95
+ cells = notebook.get("cells", [])
96
+ for idx, cell in enumerate(cells):
97
+ current_type = cell.get("cell_type", "code")
98
+
99
+ # Filter by cell type if specified
100
+ if cell_type and current_type != cell_type:
101
+ continue
102
+
103
+ source = cell.get("source", [])
104
+ if isinstance(source, list):
105
+ source = "".join(source)
106
+
107
+ if compiled.search(source):
108
+ # Find matching lines
109
+ matching_lines = []
110
+ for line_num, line in enumerate(source.split("\n"), 1):
111
+ if compiled.search(line):
112
+ matching_lines.append({
113
+ "line": line_num,
114
+ "content": line.strip()[:150]
115
+ })
116
+
117
+ results.append({
118
+ "file_path": notebook_path,
119
+ "cell_index": idx,
120
+ "cell_type": current_type,
121
+ "content": source[:300] + "..." if len(source) > 300 else source,
122
+ "matching_lines": matching_lines[:5],
123
+ "match_type": "cell",
124
+ })
125
+ except Exception:
126
+ pass
127
+
128
+ return results
129
+
130
+
131
+ @tool(args_schema=SearchWorkspaceInput)
132
+ def search_workspace_tool(
133
+ pattern: str,
134
+ file_types: List[str] = None,
135
+ path: str = ".",
136
+ max_results: int = 50,
137
+ case_sensitive: bool = False,
138
+ workspace_root: str = "."
139
+ ) -> Dict[str, Any]:
140
+ """
141
+ Search for a pattern across files in the workspace.
142
+
143
+ Searches both regular files and Jupyter notebooks.
144
+ For notebooks, searches within cell contents.
145
+
146
+ Args:
147
+ pattern: Search pattern (regex or text)
148
+ file_types: File patterns to search (default: ['*.py', '*.ipynb'])
149
+ path: Directory to search in
150
+ max_results: Maximum number of results to return
151
+ case_sensitive: Whether search is case-sensitive
152
+
153
+ Returns:
154
+ Dict with search results
155
+ """
156
+ import fnmatch
157
+
158
+ if file_types is None:
159
+ file_types = ["*.py", "*.ipynb"]
160
+
161
+ results = []
162
+ files_searched = 0
163
+
164
+ try:
165
+ search_path = os.path.normpath(os.path.join(workspace_root, path))
166
+
167
+ for root, _, filenames in os.walk(search_path):
168
+ for filename in filenames:
169
+ # Check if file matches any pattern
170
+ if not any(fnmatch.fnmatch(filename, ft) for ft in file_types):
171
+ continue
172
+
173
+ file_path = os.path.join(root, filename)
174
+ rel_path = os.path.relpath(file_path, workspace_root)
175
+ files_searched += 1
176
+
177
+ if filename.endswith(".ipynb"):
178
+ # Search in notebook
179
+ matches = _search_in_notebook(
180
+ file_path, pattern, None, case_sensitive
181
+ )
182
+ for m in matches:
183
+ m["file_path"] = rel_path
184
+ results.extend(matches)
185
+ else:
186
+ # Search in regular file
187
+ matches = _search_in_file(file_path, pattern, case_sensitive)
188
+ for m in matches:
189
+ m["file_path"] = rel_path
190
+ results.extend(matches)
191
+
192
+ if len(results) >= max_results:
193
+ break
194
+
195
+ if len(results) >= max_results:
196
+ break
197
+
198
+ return {
199
+ "tool": "search_workspace",
200
+ "success": True,
201
+ "pattern": pattern,
202
+ "path": path,
203
+ "files_searched": files_searched,
204
+ "total_results": len(results),
205
+ "results": results[:max_results],
206
+ "truncated": len(results) > max_results,
207
+ }
208
+
209
+ except Exception as e:
210
+ return {
211
+ "tool": "search_workspace",
212
+ "success": False,
213
+ "error": f"Search failed: {str(e)}",
214
+ "pattern": pattern,
215
+ }
216
+
217
+
218
+ @tool(args_schema=SearchNotebookCellsInput)
219
+ def search_notebook_cells_tool(
220
+ pattern: str,
221
+ notebook_path: Optional[str] = None,
222
+ cell_type: Optional[str] = None,
223
+ max_results: int = 30,
224
+ case_sensitive: bool = False,
225
+ workspace_root: str = "."
226
+ ) -> Dict[str, Any]:
227
+ """
228
+ Search for a pattern in Jupyter notebook cells.
229
+
230
+ Can search a specific notebook or all notebooks in workspace.
231
+ Optionally filter by cell type (code/markdown).
232
+
233
+ Args:
234
+ pattern: Search pattern (regex or text)
235
+ notebook_path: Specific notebook to search (None = all)
236
+ cell_type: Filter by cell type ('code', 'markdown', or None)
237
+ max_results: Maximum number of results
238
+ case_sensitive: Whether search is case-sensitive
239
+
240
+ Returns:
241
+ Dict with matching cells
242
+ """
243
+ results = []
244
+ notebooks_searched = 0
245
+
246
+ try:
247
+ if notebook_path:
248
+ # Search specific notebook
249
+ full_path = os.path.normpath(
250
+ os.path.join(workspace_root, notebook_path)
251
+ )
252
+ if os.path.exists(full_path) and full_path.endswith(".ipynb"):
253
+ matches = _search_in_notebook(
254
+ full_path, pattern, cell_type, case_sensitive
255
+ )
256
+ for m in matches:
257
+ m["file_path"] = notebook_path
258
+ results.extend(matches)
259
+ notebooks_searched = 1
260
+ else:
261
+ # Search all notebooks
262
+ for root, _, filenames in os.walk(workspace_root):
263
+ for filename in filenames:
264
+ if not filename.endswith(".ipynb"):
265
+ continue
266
+
267
+ file_path = os.path.join(root, filename)
268
+ rel_path = os.path.relpath(file_path, workspace_root)
269
+ notebooks_searched += 1
270
+
271
+ matches = _search_in_notebook(
272
+ file_path, pattern, cell_type, case_sensitive
273
+ )
274
+ for m in matches:
275
+ m["file_path"] = rel_path
276
+ results.extend(matches)
277
+
278
+ if len(results) >= max_results:
279
+ break
280
+
281
+ if len(results) >= max_results:
282
+ break
283
+
284
+ return {
285
+ "tool": "search_notebook_cells",
286
+ "success": True,
287
+ "pattern": pattern,
288
+ "notebook_path": notebook_path,
289
+ "cell_type": cell_type,
290
+ "notebooks_searched": notebooks_searched,
291
+ "total_results": len(results),
292
+ "results": results[:max_results],
293
+ "truncated": len(results) > max_results,
294
+ }
295
+
296
+ except Exception as e:
297
+ return {
298
+ "tool": "search_notebook_cells",
299
+ "success": False,
300
+ "error": f"Search failed: {str(e)}",
301
+ "pattern": pattern,
302
+ }
303
+
304
+
305
+ # Export all tools
306
+ SEARCH_TOOLS = [
307
+ search_workspace_tool,
308
+ search_notebook_cells_tool,
309
+ ]
agent_server/main.py CHANGED
@@ -16,6 +16,14 @@ from fastapi.middleware.cors import CORSMiddleware
16
16
 
17
17
  from agent_server.routers import agent, chat, config, file_resolver, health, rag
18
18
 
19
+ # Optional LangChain router (requires langchain dependencies)
20
+ try:
21
+ from agent_server.routers import langchain_agent
22
+ LANGCHAIN_AVAILABLE = True
23
+ except ImportError:
24
+ LANGCHAIN_AVAILABLE = False
25
+ langchain_agent = None
26
+
19
27
  # Configure logging
20
28
  logging.basicConfig(
21
29
  level=logging.INFO,
@@ -143,6 +151,11 @@ app.include_router(chat.router, prefix="/chat", tags=["Chat"])
143
151
  app.include_router(rag.router, prefix="/rag", tags=["RAG"])
144
152
  app.include_router(file_resolver.router, prefix="/file", tags=["File Resolution"])
145
153
 
154
+ # Register LangChain agent router if available
155
+ if LANGCHAIN_AVAILABLE:
156
+ app.include_router(langchain_agent.router, prefix="/agent", tags=["LangChain Agent"])
157
+ logger.info("LangChain agent router registered")
158
+
146
159
 
147
160
  def run():
148
161
  """Entry point for `hdsp-agent-server` CLI command"""
@@ -32,6 +32,20 @@ async def health_check() -> HealthResponse:
32
32
  )
33
33
 
34
34
 
35
+ @router.get("/status", response_model=HealthResponse)
36
+ async def status_check() -> HealthResponse:
37
+ """
38
+ Status check endpoint (alias for health check).
39
+
40
+ Returns server status, timestamp, and version.
41
+ """
42
+ return HealthResponse(
43
+ status="healthy",
44
+ timestamp=datetime.utcnow().isoformat(),
45
+ version="1.0.0",
46
+ )
47
+
48
+
35
49
  @router.get("/")
36
50
  async def root():
37
51
  """Root endpoint with server information"""