hdsp-jupyter-extension 2.0.5__py3-none-any.whl → 2.0.7__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 (90) hide show
  1. agent_server/core/reflection_engine.py +0 -1
  2. agent_server/knowledge/watchdog_service.py +1 -1
  3. agent_server/langchain/ARCHITECTURE.md +1193 -0
  4. agent_server/langchain/agent.py +74 -551
  5. agent_server/langchain/custom_middleware.py +636 -0
  6. agent_server/langchain/executors/__init__.py +2 -7
  7. agent_server/langchain/executors/notebook_searcher.py +46 -38
  8. agent_server/langchain/hitl_config.py +66 -0
  9. agent_server/langchain/llm_factory.py +166 -0
  10. agent_server/langchain/logging_utils.py +184 -0
  11. agent_server/langchain/prompts.py +119 -0
  12. agent_server/langchain/state.py +16 -6
  13. agent_server/langchain/tools/__init__.py +6 -0
  14. agent_server/langchain/tools/file_tools.py +91 -129
  15. agent_server/langchain/tools/jupyter_tools.py +18 -18
  16. agent_server/langchain/tools/resource_tools.py +161 -0
  17. agent_server/langchain/tools/search_tools.py +198 -216
  18. agent_server/langchain/tools/shell_tools.py +54 -0
  19. agent_server/main.py +4 -1
  20. agent_server/routers/health.py +1 -1
  21. agent_server/routers/langchain_agent.py +941 -305
  22. hdsp_agent_core/prompts/auto_agent_prompts.py +3 -3
  23. {hdsp_jupyter_extension-2.0.5.data → hdsp_jupyter_extension-2.0.7.data}/data/share/jupyter/labextensions/hdsp-agent/build_log.json +1 -1
  24. {hdsp_jupyter_extension-2.0.5.data → hdsp_jupyter_extension-2.0.7.data}/data/share/jupyter/labextensions/hdsp-agent/package.json +2 -2
  25. hdsp_jupyter_extension-2.0.5.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.8cc4873c413ed56ff485.js → hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.4770ec0fb2d173b6deb4.js +314 -8
  26. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.4770ec0fb2d173b6deb4.js.map +1 -0
  27. hdsp_jupyter_extension-2.0.5.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.a223ea20056954479ae9.js → hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.29cf4312af19e86f82af.js +1547 -330
  28. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.29cf4312af19e86f82af.js.map +1 -0
  29. hdsp_jupyter_extension-2.0.5.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.37299706f55c6d46099d.js → hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.61343eb4cf0577e74b50.js +8 -8
  30. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.61343eb4cf0577e74b50.js.map +1 -0
  31. hdsp_jupyter_extension-2.0.5.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js → hdsp_jupyter_extension-2.0.7.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 +209 -2
  32. hdsp_jupyter_extension-2.0.7.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 +1 -0
  33. jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js → hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js +2 -209
  34. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js.map +1 -0
  35. hdsp_jupyter_extension-2.0.5.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js → hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js +3 -212
  36. hdsp_jupyter_extension-2.0.7.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js.map +1 -0
  37. {hdsp_jupyter_extension-2.0.5.dist-info → hdsp_jupyter_extension-2.0.7.dist-info}/METADATA +2 -1
  38. {hdsp_jupyter_extension-2.0.5.dist-info → hdsp_jupyter_extension-2.0.7.dist-info}/RECORD +71 -68
  39. jupyter_ext/_version.py +1 -1
  40. jupyter_ext/handlers.py +1176 -58
  41. jupyter_ext/labextension/build_log.json +1 -1
  42. jupyter_ext/labextension/package.json +2 -2
  43. jupyter_ext/labextension/static/{frontend_styles_index_js.8cc4873c413ed56ff485.js → frontend_styles_index_js.4770ec0fb2d173b6deb4.js} +314 -8
  44. jupyter_ext/labextension/static/frontend_styles_index_js.4770ec0fb2d173b6deb4.js.map +1 -0
  45. jupyter_ext/labextension/static/{lib_index_js.a223ea20056954479ae9.js → lib_index_js.29cf4312af19e86f82af.js} +1547 -330
  46. jupyter_ext/labextension/static/lib_index_js.29cf4312af19e86f82af.js.map +1 -0
  47. jupyter_ext/labextension/static/{remoteEntry.37299706f55c6d46099d.js → remoteEntry.61343eb4cf0577e74b50.js} +8 -8
  48. jupyter_ext/labextension/static/remoteEntry.61343eb4cf0577e74b50.js.map +1 -0
  49. jupyter_ext/labextension/static/{vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js → vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js} +209 -2
  50. jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js.map +1 -0
  51. hdsp_jupyter_extension-2.0.5.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js → jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js +2 -209
  52. jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js.map +1 -0
  53. jupyter_ext/labextension/static/{vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js → vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js} +3 -212
  54. jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js.map +1 -0
  55. jupyter_ext/resource_usage.py +180 -0
  56. jupyter_ext/tests/test_handlers.py +58 -0
  57. agent_server/langchain/executors/jupyter_executor.py +0 -429
  58. agent_server/langchain/middleware/__init__.py +0 -36
  59. agent_server/langchain/middleware/code_search_middleware.py +0 -278
  60. agent_server/langchain/middleware/error_handling_middleware.py +0 -338
  61. agent_server/langchain/middleware/jupyter_execution_middleware.py +0 -301
  62. agent_server/langchain/middleware/rag_middleware.py +0 -227
  63. agent_server/langchain/middleware/validation_middleware.py +0 -240
  64. hdsp_jupyter_extension-2.0.5.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.8cc4873c413ed56ff485.js.map +0 -1
  65. hdsp_jupyter_extension-2.0.5.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.a223ea20056954479ae9.js.map +0 -1
  66. hdsp_jupyter_extension-2.0.5.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.37299706f55c6d46099d.js.map +0 -1
  67. hdsp_jupyter_extension-2.0.5.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js.map +0 -1
  68. hdsp_jupyter_extension-2.0.5.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js.map +0 -1
  69. hdsp_jupyter_extension-2.0.5.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js.map +0 -1
  70. jupyter_ext/labextension/static/frontend_styles_index_js.8cc4873c413ed56ff485.js.map +0 -1
  71. jupyter_ext/labextension/static/lib_index_js.a223ea20056954479ae9.js.map +0 -1
  72. jupyter_ext/labextension/static/remoteEntry.37299706f55c6d46099d.js.map +0 -1
  73. jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js.24edcc52a1c014a8a5f0.js.map +0 -1
  74. jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.19ecf6babe00caff6b8a.js.map +0 -1
  75. jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.1f5038488cdfd8b3a85d.js.map +0 -1
  76. {hdsp_jupyter_extension-2.0.5.data → hdsp_jupyter_extension-2.0.7.data}/data/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +0 -0
  77. {hdsp_jupyter_extension-2.0.5.data → hdsp_jupyter_extension-2.0.7.data}/data/share/jupyter/labextensions/hdsp-agent/install.json +0 -0
  78. {hdsp_jupyter_extension-2.0.5.data → hdsp_jupyter_extension-2.0.7.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
  79. {hdsp_jupyter_extension-2.0.5.data → hdsp_jupyter_extension-2.0.7.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
  80. {hdsp_jupyter_extension-2.0.5.data → hdsp_jupyter_extension-2.0.7.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
  81. {hdsp_jupyter_extension-2.0.5.data → hdsp_jupyter_extension-2.0.7.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
  82. {hdsp_jupyter_extension-2.0.5.data → hdsp_jupyter_extension-2.0.7.data}/data/share/jupyter/labextensions/hdsp-agent/static/style.js +0 -0
  83. {hdsp_jupyter_extension-2.0.5.data → hdsp_jupyter_extension-2.0.7.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
  84. {hdsp_jupyter_extension-2.0.5.data → hdsp_jupyter_extension-2.0.7.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
  85. {hdsp_jupyter_extension-2.0.5.data → hdsp_jupyter_extension-2.0.7.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js +0 -0
  86. {hdsp_jupyter_extension-2.0.5.data → hdsp_jupyter_extension-2.0.7.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
  87. {hdsp_jupyter_extension-2.0.5.data → hdsp_jupyter_extension-2.0.7.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +0 -0
  88. {hdsp_jupyter_extension-2.0.5.data → hdsp_jupyter_extension-2.0.7.data}/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js.map +0 -0
  89. {hdsp_jupyter_extension-2.0.5.dist-info → hdsp_jupyter_extension-2.0.7.dist-info}/WHEEL +0 -0
  90. {hdsp_jupyter_extension-2.0.5.dist-info → hdsp_jupyter_extension-2.0.7.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,119 @@
1
+ """
2
+ Prompt templates for LangChain agent.
3
+
4
+ Contains system prompts, JSON schema for fallback tool calling,
5
+ and middleware-specific prompts.
6
+ """
7
+
8
+ DEFAULT_SYSTEM_PROMPT = """You are an expert Python data scientist and Jupyter notebook assistant.
9
+ Your role is to help users with data analysis, visualization, and Python coding tasks in Jupyter notebooks. You can use only Korean
10
+
11
+ ## ⚠️ CRITICAL RULE: NEVER produce an empty response
12
+
13
+ You MUST ALWAYS call a tool in every response. After any tool result, you MUST:
14
+ 1. Check your todo list - are there pending or in_progress items?
15
+ 2. If YES → call the next appropriate tool (jupyter_cell_tool, markdown_tool, etc.)
16
+ 3. When you suggest next steps for todo item '다음 단계 제시', you MUST create next steps in json format matching this schema:
17
+ {
18
+ "next_items": [
19
+ {
20
+ "subject": "<subject for next step>",
21
+ "description": "<detailed description for the next step>"
22
+ }, ...
23
+ ]
24
+ }
25
+ 4. If ALL todos are completed → call final_answer_tool with a summary
26
+
27
+ NEVER end your turn without calling a tool. NEVER produce an empty response.
28
+
29
+ ## 🔴 MANDATORY: Resource Check Before Data Hanlding
30
+ **ALWAYS call check_resource_tool FIRST** when the task involves:
31
+ - Loading files: .csv, .parquet, .json, .xlsx, .pickle, .h5, .feather
32
+ - Handling datasets(dataframe) with pandas, polars, dask, or similar libraries
33
+ - Training ML models on data files
34
+
35
+ ## Mandatory Workflow
36
+ 1. After EVERY tool result, immediately call the next tool
37
+ 2. Continue until ALL todos show status: "completed"
38
+ 3. ONLY THEN call final_answer_tool to summarize
39
+ 4. Only use jupyter_cell_tool for Python code or when the user explicitly asks to run in a notebook cell
40
+ 5. For plots and charts, use English text only.
41
+
42
+ ## ❌ FORBIDDEN (will break the workflow)
43
+ - Producing an empty response (no tool call, no content)
44
+ - Stopping after any tool without calling the next tool
45
+ - Ending without calling final_answer_tool
46
+ - Leaving todos in "in_progress" or "pending" state without continuing
47
+
48
+ ## 🚫 execute_command_tool Rules
49
+ **NEVER run long-running commands** with execute_command_tool (e.g., servers, daemons, watch processes).
50
+ - ✅ Allowed: Quick commands like `ls`, `cat`, `grep`, `git status`
51
+ - ❌ Forbidden: `jupyter lab`, `npm start`, `python app.py`, `watch`, background processes
52
+ - For long tasks: Use jupyter_cell_tool instead or inform the user to run manually
53
+ """
54
+
55
+ JSON_TOOL_SCHEMA = """You MUST respond with ONLY valid JSON matching this schema:
56
+ {
57
+ "tool": "<tool_name>",
58
+ "arguments": {"arg1": "value1", ...}
59
+ }
60
+
61
+ Available tools:
62
+ - jupyter_cell_tool: Execute Python code. Arguments: {"code": "<python_code>"}
63
+ - markdown_tool: Add markdown cell. Arguments: {"content": "<markdown>"}
64
+ - final_answer_tool: Complete task. Arguments: {"answer": "<summary>"}
65
+ - write_todos: Update task list. Arguments: {"todos": [{"content": "...", "status": "pending|in_progress|completed"}]}
66
+ - read_file_tool: Read file. Arguments: {"path": "<file_path>"}
67
+ - write_file_tool: Write file. Arguments: {"path": "<path>", "content": "<content>", "overwrite": false}
68
+ - list_files_tool: List directory. Arguments: {"path": ".", "recursive": false}
69
+ - search_workspace_tool: Search files. Arguments: {"pattern": "<regex>", "file_types": ["py"], "path": "."}
70
+ - search_notebook_cells_tool: Search notebook cells. Arguments: {"pattern": "<regex>"}
71
+ - execute_command_tool: Execute shell command. Arguments: {"command": "<command>", "stdin": "<input_for_prompts>"}
72
+ - check_resource_tool: Check resources before data processing. Arguments: {"files": ["<path>"], "dataframes": ["<var_name>"]}
73
+
74
+ Output ONLY the JSON object, no markdown, no explanation."""
75
+
76
+ TODO_LIST_SYSTEM_PROMPT = """
77
+ ## CRITICAL WORKFLOW RULES - MUST FOLLOW:
78
+ 1. NEVER stop after calling write_todos - ALWAYS make another tool call immediately
79
+ 2. write_todos is ONLY for tracking progress - it does NOT complete any work
80
+ 3. After EVERY write_todos call, you MUST call another tool (jupyter_cell_tool, markdown_tool, or final_answer_tool)
81
+
82
+ ## Todo List Management:
83
+ - Before complex tasks, use write_todos to create a task list
84
+ - Update todos as you complete each step (mark 'in_progress' → 'completed')
85
+ - Each todo item should be specific and descriptive (30-60 characters)
86
+ - All todo items must be written in Korean
87
+ - ALWAYS include "다음 단계 제시" as the LAST item
88
+
89
+ ## Task Completion Flow:
90
+ 1. When current task is done → mark it 'completed' with write_todos
91
+ 2. IMMEDIATELY call the next tool (jupyter_cell_tool for code, markdown_tool for text)
92
+ 3. For "다음 단계 제시" → mark completed, then call final_answer_tool with suggestions
93
+ 4. NEVER end your turn after write_todos - you MUST continue with actual work
94
+
95
+ ## FORBIDDEN PATTERNS:
96
+ ❌ Calling write_todos and then stopping
97
+ ❌ Updating todo status without doing the actual work
98
+ ❌ Ending turn without calling final_answer_tool when all tasks are done
99
+ """
100
+
101
+ TODO_LIST_TOOL_DESCRIPTION = """Update the task list for tracking progress.
102
+ ⚠️ CRITICAL: This tool is ONLY for tracking - it does NOT do any actual work.
103
+ After calling this tool, you MUST IMMEDIATELY call another tool (jupyter_cell_tool, markdown_tool, or final_answer_tool).
104
+ NEVER end your response after calling write_todos - always continue with the next action tool."""
105
+
106
+ # Non-HITL tools that execute immediately without user approval
107
+ NON_HITL_TOOLS = {
108
+ "markdown_tool",
109
+ "markdown",
110
+ "read_file_tool",
111
+ "read_file",
112
+ "list_files_tool",
113
+ "list_files",
114
+ "search_workspace_tool",
115
+ "search_workspace",
116
+ "search_notebook_cells_tool",
117
+ "search_notebook_cells",
118
+ "write_todos",
119
+ }
@@ -6,7 +6,7 @@ This state is passed through the agent execution and updated by middleware.
6
6
  """
7
7
 
8
8
  from dataclasses import dataclass
9
- from typing import Annotated, Any, Dict, List, Optional, TypedDict
9
+ from typing import Annotated, Any, Dict, List, Optional, TypedDict, Union
10
10
 
11
11
  from langchain_core.messages import BaseMessage
12
12
  from langgraph.graph.message import add_messages
@@ -14,6 +14,7 @@ from langgraph.graph.message import add_messages
14
14
 
15
15
  class NotebookContext(TypedDict, total=False):
16
16
  """Current notebook context"""
17
+
17
18
  notebook_path: str
18
19
  cell_count: int
19
20
  imported_libraries: List[str]
@@ -24,6 +25,7 @@ class NotebookContext(TypedDict, total=False):
24
25
 
25
26
  class ExecutionResult(TypedDict, total=False):
26
27
  """Result of code execution"""
28
+
27
29
  success: bool
28
30
  output: str
29
31
  error_type: Optional[str]
@@ -35,6 +37,7 @@ class ExecutionResult(TypedDict, total=False):
35
37
 
36
38
  class SearchResult(TypedDict, total=False):
37
39
  """Result of code search"""
40
+
38
41
  file_path: str
39
42
  cell_index: Optional[int]
40
43
  line_number: Optional[int]
@@ -51,6 +54,7 @@ class AgentState(TypedDict, total=False):
51
54
  - Tool executions (execution results, search results)
52
55
  - Agent decisions (current step, plan updates)
53
56
  """
57
+
54
58
  # Message history - uses add_messages reducer to accumulate messages
55
59
  messages: Annotated[List[BaseMessage], add_messages]
56
60
 
@@ -87,6 +91,9 @@ class AgentState(TypedDict, total=False):
87
91
  # Detected libraries for knowledge injection
88
92
  detected_libraries: List[str]
89
93
 
94
+ # Resource usage context (legacy, use check_resource_tool instead)
95
+ resource_context: Optional[Union[Dict[str, Any], str]]
96
+
90
97
  # Final answer
91
98
  final_answer: Optional[str]
92
99
  is_complete: bool
@@ -96,9 +103,10 @@ class AgentState(TypedDict, total=False):
96
103
  class AgentRuntime:
97
104
  """
98
105
  Runtime context passed to middleware.
99
-
106
+
100
107
  Contains references to executors and services needed by middleware.
101
108
  """
109
+
102
110
  jupyter_executor: Any = None
103
111
  notebook_searcher: Any = None
104
112
  rag_manager: Any = None
@@ -107,7 +115,7 @@ class AgentRuntime:
107
115
  workspace_root: str = "."
108
116
 
109
117
  # Execution mode
110
- embedded_mode: bool = True
118
+ embedded_mode: bool = False
111
119
 
112
120
  # Configuration
113
121
  max_retries: int = 3
@@ -123,19 +131,20 @@ def create_initial_state(
123
131
  ) -> AgentState:
124
132
  """
125
133
  Create initial agent state from user request.
126
-
134
+
127
135
  Args:
128
136
  user_request: Natural language request from user
129
137
  notebook_context: Current notebook state
130
138
  llm_config: LLM configuration
131
-
139
+
132
140
  Returns:
133
141
  Initialized AgentState
134
142
  """
135
143
  return AgentState(
136
144
  messages=[],
137
145
  user_request=user_request,
138
- notebook_context=notebook_context or NotebookContext(
146
+ notebook_context=notebook_context
147
+ or NotebookContext(
139
148
  notebook_path="",
140
149
  cell_count=0,
141
150
  imported_libraries=[],
@@ -154,6 +163,7 @@ def create_initial_state(
154
163
  recovery_strategy=None,
155
164
  llm_config=llm_config or {},
156
165
  detected_libraries=[],
166
+ resource_context=None,
157
167
  final_answer=None,
158
168
  is_complete=False,
159
169
  )
@@ -10,6 +10,8 @@ Tools available:
10
10
  - list_files: List directory contents
11
11
  - search_workspace: Search files in workspace
12
12
  - search_notebook_cells: Search cells in notebooks
13
+ - execute_command_tool: Run shell commands (client-executed)
14
+ - check_resource_tool: Check resources before data processing (client-executed)
13
15
  """
14
16
 
15
17
  from agent_server.langchain.tools.file_tools import (
@@ -22,10 +24,12 @@ from agent_server.langchain.tools.jupyter_tools import (
22
24
  jupyter_cell_tool,
23
25
  markdown_tool,
24
26
  )
27
+ from agent_server.langchain.tools.resource_tools import check_resource_tool
25
28
  from agent_server.langchain.tools.search_tools import (
26
29
  search_notebook_cells_tool,
27
30
  search_workspace_tool,
28
31
  )
32
+ from agent_server.langchain.tools.shell_tools import execute_command_tool
29
33
 
30
34
  __all__ = [
31
35
  "jupyter_cell_tool",
@@ -36,4 +40,6 @@ __all__ = [
36
40
  "list_files_tool",
37
41
  "search_workspace_tool",
38
42
  "search_notebook_cells_tool",
43
+ "execute_command_tool",
44
+ "check_resource_tool",
39
45
  ]
@@ -8,7 +8,7 @@ Provides tools for file system operations:
8
8
  """
9
9
 
10
10
  import os
11
- from typing import Any, Dict, List, Optional
11
+ from typing import Any, Dict, Optional
12
12
 
13
13
  from langchain_core.tools import tool
14
14
  from pydantic import BaseModel, Field
@@ -16,31 +16,54 @@ from pydantic import BaseModel, Field
16
16
 
17
17
  class ReadFileInput(BaseModel):
18
18
  """Input schema for read_file tool"""
19
+
19
20
  path: str = Field(description="Relative path to the file to read")
20
21
  encoding: str = Field(default="utf-8", description="File encoding")
22
+ max_lines: Optional[int] = Field(
23
+ default=None,
24
+ description="Maximum number of lines to read",
25
+ )
26
+ execution_result: Optional[Dict[str, Any]] = Field(
27
+ default=None,
28
+ description="Optional execution result payload from the client",
29
+ )
21
30
 
22
31
 
23
32
  class WriteFileInput(BaseModel):
24
33
  """Input schema for write_file tool"""
34
+
25
35
  path: str = Field(description="Relative path to the file to write")
26
36
  content: str = Field(description="Content to write to the file")
27
37
  encoding: str = Field(default="utf-8", description="File encoding")
38
+ overwrite: bool = Field(
39
+ default=False,
40
+ description="Whether to overwrite an existing file (default: false)",
41
+ )
42
+ execution_result: Optional[Dict[str, Any]] = Field(
43
+ default=None,
44
+ description="Optional execution result payload from the client",
45
+ )
28
46
 
29
47
 
30
48
  class ListFilesInput(BaseModel):
31
49
  """Input schema for list_files tool"""
50
+
32
51
  path: str = Field(default=".", description="Directory path to list")
33
52
  recursive: bool = Field(default=False, description="Whether to list recursively")
34
53
  pattern: Optional[str] = Field(
35
54
  default=None,
36
- description="Glob pattern to filter files (e.g., '*.py', '*.ipynb')"
55
+ description="Glob pattern to filter files (e.g., '*.py', '*.ipynb')",
56
+ )
57
+ execution_result: Optional[Dict[str, Any]] = Field(
58
+ default=None,
59
+ description="Optional execution result payload from the client",
37
60
  )
38
61
 
39
62
 
40
63
  def _validate_path(path: str, workspace_root: str = ".") -> str:
41
64
  """
42
65
  Validate and resolve file path.
43
-
66
+
44
67
  Security checks:
45
68
  - No absolute paths allowed
46
69
  - No parent directory traversal (..)
@@ -70,93 +93,88 @@ def _validate_path(path: str, workspace_root: str = ".") -> str:
70
93
  def read_file_tool(
71
94
  path: str,
72
95
  encoding: str = "utf-8",
73
- workspace_root: str = "."
96
+ max_lines: Optional[int] = None,
97
+ execution_result: Optional[Dict[str, Any]] = None,
98
+ workspace_root: str = ".",
74
99
  ) -> Dict[str, Any]:
75
100
  """
76
101
  Read content from a file.
77
-
102
+
78
103
  Only relative paths within the workspace are allowed.
79
104
  Absolute paths and parent directory traversal (..) are blocked.
80
-
105
+
81
106
  Args:
82
107
  path: Relative path to the file
83
108
  encoding: File encoding (default: utf-8)
84
-
109
+
85
110
  Returns:
86
111
  Dict with file content or error
87
112
  """
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:
113
+ if os.path.isabs(path):
119
114
  return {
120
- "tool": "read_file",
115
+ "tool": "read_file_tool",
121
116
  "success": False,
122
- "error": str(e),
117
+ "error": f"Absolute paths not allowed: {path}",
123
118
  "path": path,
124
119
  }
125
- except Exception as e:
120
+ if ".." in path:
126
121
  return {
127
- "tool": "read_file",
122
+ "tool": "read_file_tool",
128
123
  "success": False,
129
- "error": f"Failed to read file: {str(e)}",
124
+ "error": f"Parent directory traversal not allowed: {path}",
130
125
  "path": path,
131
126
  }
132
127
 
128
+ response: Dict[str, Any] = {
129
+ "tool": "read_file_tool",
130
+ "parameters": {
131
+ "path": path,
132
+ "encoding": encoding,
133
+ "max_lines": max_lines,
134
+ },
135
+ "status": "pending_execution",
136
+ "message": "File read queued for execution by client",
137
+ }
138
+ if execution_result is not None:
139
+ response["execution_result"] = execution_result
140
+ response["status"] = "complete"
141
+ response["message"] = "File read executed with client-reported results"
142
+ return response
143
+
133
144
 
134
145
  @tool(args_schema=WriteFileInput)
135
146
  def write_file_tool(
136
147
  path: str,
137
148
  content: str,
138
149
  encoding: str = "utf-8",
139
- workspace_root: str = "."
150
+ overwrite: bool = False,
151
+ execution_result: Optional[Dict[str, Any]] = None,
152
+ workspace_root: str = ".",
140
153
  ) -> Dict[str, Any]:
141
154
  """
142
155
  Write content to a file.
143
-
156
+
144
157
  This operation requires user approval before execution.
145
158
  Only relative paths within the workspace are allowed.
146
-
159
+
147
160
  Args:
148
161
  path: Relative path to the file
149
162
  content: Content to write
150
163
  encoding: File encoding (default: utf-8)
151
-
164
+
152
165
  Returns:
153
166
  Dict with operation status (pending approval)
154
167
  """
155
168
  try:
156
169
  resolved_path = _validate_path(path, workspace_root)
157
170
 
158
- return {
159
- "tool": "write_file",
171
+ response: Dict[str, Any] = {
172
+ "tool": "write_file_tool",
173
+ "parameters": {
174
+ "path": path,
175
+ "encoding": encoding,
176
+ "overwrite": overwrite,
177
+ },
160
178
  "status": "pending_approval",
161
179
  "path": path,
162
180
  "resolved_path": resolved_path,
@@ -164,10 +182,15 @@ def write_file_tool(
164
182
  "content_length": len(content),
165
183
  "message": "File write operation requires user approval",
166
184
  }
185
+ if execution_result is not None:
186
+ response["execution_result"] = execution_result
187
+ response["status"] = "complete"
188
+ response["message"] = "File write executed with client-reported results"
189
+ return response
167
190
 
168
191
  except ValueError as e:
169
192
  return {
170
- "tool": "write_file",
193
+ "tool": "write_file_tool",
171
194
  "success": False,
172
195
  "error": str(e),
173
196
  "path": path,
@@ -179,96 +202,35 @@ def list_files_tool(
179
202
  path: str = ".",
180
203
  recursive: bool = False,
181
204
  pattern: Optional[str] = None,
182
- workspace_root: str = "."
205
+ execution_result: Optional[Dict[str, Any]] = None,
206
+ workspace_root: str = ".",
183
207
  ) -> Dict[str, Any]:
184
208
  """
185
209
  List files and directories.
186
-
210
+
187
211
  Args:
188
212
  path: Directory path to list (default: current directory)
189
213
  recursive: Whether to list recursively
190
214
  pattern: Optional glob pattern to filter (e.g., '*.py')
191
-
215
+
192
216
  Returns:
193
217
  Dict with list of files and directories
194
218
  """
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,
219
+ response: Dict[str, Any] = {
220
+ "tool": "list_files_tool",
221
+ "parameters": {
251
222
  "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
- }
223
+ "recursive": recursive,
224
+ "pattern": pattern,
225
+ },
226
+ "status": "pending_execution",
227
+ "message": "File listing queued for execution by client",
228
+ }
229
+ if execution_result is not None:
230
+ response["execution_result"] = execution_result
231
+ response["status"] = "complete"
232
+ response["message"] = "File listing executed with client-reported results"
233
+ return response
272
234
 
273
235
 
274
236
  # Export all tools