hdsp-jupyter-extension 2.0.0__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/__init__.py +8 -0
- agent_server/core/__init__.py +92 -0
- agent_server/core/api_key_manager.py +427 -0
- agent_server/core/code_validator.py +1238 -0
- agent_server/core/context_condenser.py +308 -0
- agent_server/core/embedding_service.py +254 -0
- agent_server/core/error_classifier.py +577 -0
- agent_server/core/llm_client.py +95 -0
- agent_server/core/llm_service.py +649 -0
- agent_server/core/notebook_generator.py +274 -0
- agent_server/core/prompt_builder.py +35 -0
- agent_server/core/rag_manager.py +742 -0
- agent_server/core/reflection_engine.py +489 -0
- agent_server/core/retriever.py +248 -0
- agent_server/core/state_verifier.py +452 -0
- agent_server/core/summary_generator.py +484 -0
- agent_server/core/task_manager.py +198 -0
- agent_server/knowledge/__init__.py +9 -0
- agent_server/knowledge/watchdog_service.py +352 -0
- agent_server/main.py +160 -0
- agent_server/prompts/__init__.py +60 -0
- agent_server/prompts/file_action_prompts.py +113 -0
- agent_server/routers/__init__.py +9 -0
- agent_server/routers/agent.py +591 -0
- agent_server/routers/chat.py +188 -0
- agent_server/routers/config.py +100 -0
- agent_server/routers/file_resolver.py +293 -0
- agent_server/routers/health.py +42 -0
- agent_server/routers/rag.py +163 -0
- agent_server/schemas/__init__.py +60 -0
- hdsp_agent_core/__init__.py +158 -0
- hdsp_agent_core/factory.py +252 -0
- hdsp_agent_core/interfaces.py +203 -0
- hdsp_agent_core/knowledge/__init__.py +31 -0
- hdsp_agent_core/knowledge/chunking.py +356 -0
- hdsp_agent_core/knowledge/libraries/dask.md +188 -0
- hdsp_agent_core/knowledge/libraries/matplotlib.md +164 -0
- hdsp_agent_core/knowledge/libraries/polars.md +68 -0
- hdsp_agent_core/knowledge/loader.py +337 -0
- hdsp_agent_core/llm/__init__.py +13 -0
- hdsp_agent_core/llm/service.py +556 -0
- hdsp_agent_core/managers/__init__.py +22 -0
- hdsp_agent_core/managers/config_manager.py +133 -0
- hdsp_agent_core/managers/session_manager.py +251 -0
- hdsp_agent_core/models/__init__.py +115 -0
- hdsp_agent_core/models/agent.py +316 -0
- hdsp_agent_core/models/chat.py +41 -0
- hdsp_agent_core/models/common.py +95 -0
- hdsp_agent_core/models/rag.py +368 -0
- hdsp_agent_core/prompts/__init__.py +63 -0
- hdsp_agent_core/prompts/auto_agent_prompts.py +1260 -0
- hdsp_agent_core/prompts/cell_action_prompts.py +98 -0
- hdsp_agent_core/services/__init__.py +18 -0
- hdsp_agent_core/services/agent_service.py +438 -0
- hdsp_agent_core/services/chat_service.py +205 -0
- hdsp_agent_core/services/rag_service.py +262 -0
- hdsp_agent_core/tests/__init__.py +1 -0
- hdsp_agent_core/tests/conftest.py +102 -0
- hdsp_agent_core/tests/test_factory.py +251 -0
- hdsp_agent_core/tests/test_services.py +326 -0
- hdsp_jupyter_extension-2.0.0.data/data/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +7 -0
- hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/build_log.json +738 -0
- hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/install.json +5 -0
- hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/package.json +134 -0
- hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2607ff74c74acfa83158.js +4369 -0
- hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2607ff74c74acfa83158.js.map +1 -0
- hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.622c1a5918b3aafb2315.js +12496 -0
- hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.622c1a5918b3aafb2315.js.map +1 -0
- hdsp_jupyter_extension-2.0.0.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 +94 -0
- hdsp_jupyter_extension-2.0.0.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 +1 -0
- hdsp_jupyter_extension-2.0.0.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 +94 -0
- hdsp_jupyter_extension-2.0.0.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 +1 -0
- hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.dae97cde171e13b8c834.js +623 -0
- hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.dae97cde171e13b8c834.js.map +1 -0
- hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/style.js +4 -0
- hdsp_jupyter_extension-2.0.0.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 +507 -0
- hdsp_jupyter_extension-2.0.0.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 +1 -0
- 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 +2071 -0
- 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 +1 -0
- 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 +1059 -0
- 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 +1 -0
- hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js +376 -0
- hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js.map +1 -0
- hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +60336 -0
- hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js.map +1 -0
- hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js +7132 -0
- 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 +1 -0
- hdsp_jupyter_extension-2.0.0.dist-info/METADATA +152 -0
- hdsp_jupyter_extension-2.0.0.dist-info/RECORD +121 -0
- hdsp_jupyter_extension-2.0.0.dist-info/WHEEL +4 -0
- hdsp_jupyter_extension-2.0.0.dist-info/licenses/LICENSE +21 -0
- jupyter_ext/__init__.py +233 -0
- jupyter_ext/_version.py +4 -0
- jupyter_ext/config.py +111 -0
- jupyter_ext/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +7 -0
- jupyter_ext/handlers.py +632 -0
- jupyter_ext/labextension/build_log.json +738 -0
- jupyter_ext/labextension/package.json +134 -0
- jupyter_ext/labextension/static/frontend_styles_index_js.2607ff74c74acfa83158.js +4369 -0
- jupyter_ext/labextension/static/frontend_styles_index_js.2607ff74c74acfa83158.js.map +1 -0
- jupyter_ext/labextension/static/lib_index_js.622c1a5918b3aafb2315.js +12496 -0
- jupyter_ext/labextension/static/lib_index_js.622c1a5918b3aafb2315.js.map +1 -0
- jupyter_ext/labextension/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b80.c095373419d05e6f141a.js +94 -0
- jupyter_ext/labextension/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b80.c095373419d05e6f141a.js.map +1 -0
- jupyter_ext/labextension/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b81.61e75fb98ecff46cf836.js +94 -0
- jupyter_ext/labextension/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b81.61e75fb98ecff46cf836.js.map +1 -0
- jupyter_ext/labextension/static/remoteEntry.dae97cde171e13b8c834.js +623 -0
- jupyter_ext/labextension/static/remoteEntry.dae97cde171e13b8c834.js.map +1 -0
- jupyter_ext/labextension/static/style.js +4 -0
- jupyter_ext/labextension/static/vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195.e2553aab0c3963b83dd7.js +507 -0
- jupyter_ext/labextension/static/vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195.e2553aab0c3963b83dd7.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 +2071 -0
- jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js.map +1 -0
- jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js +1059 -0
- jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js.map +1 -0
- jupyter_ext/labextension/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js +376 -0
- jupyter_ext/labextension/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js.map +1 -0
- jupyter_ext/labextension/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +60336 -0
- jupyter_ext/labextension/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js.map +1 -0
- jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js +7132 -0
- jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js.map +1 -0
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Watchdog Service - File system monitoring for automatic RAG re-indexing.
|
|
3
|
+
|
|
4
|
+
Features:
|
|
5
|
+
- Monitors knowledge base directory for changes
|
|
6
|
+
- Debounces rapid file changes
|
|
7
|
+
- Triggers incremental re-indexing
|
|
8
|
+
- Pattern-based file filtering
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
import logging
|
|
13
|
+
import threading
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import TYPE_CHECKING, Callable, Optional, Set
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from hdsp_agent_core.models.rag import WatchdogConfig
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class WatchdogService:
|
|
25
|
+
"""
|
|
26
|
+
File system monitoring service for RAG knowledge base.
|
|
27
|
+
|
|
28
|
+
Features:
|
|
29
|
+
- Asynchronous file change detection
|
|
30
|
+
- Debouncing to prevent excessive re-indexing
|
|
31
|
+
- Pattern-based filtering (*.md, *.py, etc.)
|
|
32
|
+
- Ignore patterns for cache/build directories
|
|
33
|
+
- Callback-based notification
|
|
34
|
+
|
|
35
|
+
Usage:
|
|
36
|
+
service = WatchdogService(config, on_change_callback)
|
|
37
|
+
service.start("/path/to/knowledge")
|
|
38
|
+
# ... later
|
|
39
|
+
service.stop()
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
config: "WatchdogConfig",
|
|
45
|
+
on_change_callback: Optional[Callable[[Set[Path]], None]] = None,
|
|
46
|
+
):
|
|
47
|
+
self._config = config
|
|
48
|
+
self._on_change = on_change_callback
|
|
49
|
+
self._observer = None
|
|
50
|
+
self._running = False
|
|
51
|
+
self._pending_changes: Set[Path] = set()
|
|
52
|
+
self._last_change_time: Optional[datetime] = None
|
|
53
|
+
self._debounce_task: Optional[asyncio.Task] = None
|
|
54
|
+
self._lock = threading.Lock()
|
|
55
|
+
self._loop: Optional[asyncio.AbstractEventLoop] = None
|
|
56
|
+
|
|
57
|
+
def start(self, watch_path: Path) -> bool:
|
|
58
|
+
"""
|
|
59
|
+
Start monitoring the specified directory.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
watch_path: Directory to monitor
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
True if monitoring started successfully
|
|
66
|
+
"""
|
|
67
|
+
if not self._config.enabled:
|
|
68
|
+
logger.info("Watchdog disabled by configuration")
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
if self._running:
|
|
72
|
+
logger.warning("Watchdog already running")
|
|
73
|
+
return True
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
from watchdog.events import FileSystemEvent, FileSystemEventHandler
|
|
77
|
+
from watchdog.observers import Observer
|
|
78
|
+
except ImportError:
|
|
79
|
+
logger.warning(
|
|
80
|
+
"watchdog not installed, file monitoring disabled. "
|
|
81
|
+
"Install with: pip install watchdog"
|
|
82
|
+
)
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
if not watch_path.exists():
|
|
86
|
+
logger.warning(f"Watch path does not exist: {watch_path}")
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
# Store event loop for async callbacks
|
|
90
|
+
try:
|
|
91
|
+
self._loop = asyncio.get_running_loop()
|
|
92
|
+
except RuntimeError:
|
|
93
|
+
self._loop = None
|
|
94
|
+
|
|
95
|
+
# Create event handler
|
|
96
|
+
handler = self._create_event_handler(FileSystemEventHandler)
|
|
97
|
+
|
|
98
|
+
# Start observer
|
|
99
|
+
self._observer = Observer()
|
|
100
|
+
self._observer.schedule(handler, str(watch_path), recursive=True)
|
|
101
|
+
self._observer.start()
|
|
102
|
+
self._running = True
|
|
103
|
+
|
|
104
|
+
logger.info(f"Watchdog started monitoring: {watch_path}")
|
|
105
|
+
return True
|
|
106
|
+
|
|
107
|
+
def _create_event_handler(self, base_class):
|
|
108
|
+
"""Create a watchdog event handler class."""
|
|
109
|
+
service = self
|
|
110
|
+
|
|
111
|
+
class RAGEventHandler(base_class):
|
|
112
|
+
def on_any_event(self, event):
|
|
113
|
+
if event.is_directory:
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
# Get file path
|
|
117
|
+
file_path = Path(event.src_path)
|
|
118
|
+
|
|
119
|
+
# Check if file matches patterns
|
|
120
|
+
if not service._should_process(file_path):
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
# Queue the change
|
|
124
|
+
service._queue_change(file_path)
|
|
125
|
+
|
|
126
|
+
return RAGEventHandler()
|
|
127
|
+
|
|
128
|
+
def _should_process(self, file_path: Path) -> bool:
|
|
129
|
+
"""Check if file should trigger re-indexing."""
|
|
130
|
+
file_name = file_path.name
|
|
131
|
+
|
|
132
|
+
# Check ignore patterns
|
|
133
|
+
for pattern in self._config.ignore_patterns:
|
|
134
|
+
if file_name.startswith(pattern.replace("*", "")):
|
|
135
|
+
return False
|
|
136
|
+
if pattern.startswith("*") and file_name.endswith(pattern[1:]):
|
|
137
|
+
return False
|
|
138
|
+
|
|
139
|
+
# Check include patterns
|
|
140
|
+
for pattern in self._config.patterns:
|
|
141
|
+
if pattern.startswith("*"):
|
|
142
|
+
suffix = pattern[1:]
|
|
143
|
+
if file_name.endswith(suffix):
|
|
144
|
+
return True
|
|
145
|
+
elif file_name == pattern:
|
|
146
|
+
return True
|
|
147
|
+
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
def _queue_change(self, file_path: Path) -> None:
|
|
151
|
+
"""Queue a file change for processing."""
|
|
152
|
+
with self._lock:
|
|
153
|
+
self._pending_changes.add(file_path)
|
|
154
|
+
self._last_change_time = datetime.now()
|
|
155
|
+
|
|
156
|
+
logger.debug(f"Queued change: {file_path}")
|
|
157
|
+
|
|
158
|
+
# Schedule debounced processing
|
|
159
|
+
if self._loop and self._loop.is_running():
|
|
160
|
+
self._loop.call_soon_threadsafe(self._schedule_debounce)
|
|
161
|
+
|
|
162
|
+
def _schedule_debounce(self) -> None:
|
|
163
|
+
"""Schedule the debounced callback."""
|
|
164
|
+
if self._debounce_task and not self._debounce_task.done():
|
|
165
|
+
self._debounce_task.cancel()
|
|
166
|
+
|
|
167
|
+
self._debounce_task = asyncio.create_task(self._debounced_callback())
|
|
168
|
+
|
|
169
|
+
async def _debounced_callback(self) -> None:
|
|
170
|
+
"""Wait for debounce period then trigger callback."""
|
|
171
|
+
try:
|
|
172
|
+
await asyncio.sleep(self._config.debounce_seconds)
|
|
173
|
+
|
|
174
|
+
# Get pending changes
|
|
175
|
+
with self._lock:
|
|
176
|
+
if not self._pending_changes:
|
|
177
|
+
return
|
|
178
|
+
|
|
179
|
+
changes = self._pending_changes.copy()
|
|
180
|
+
self._pending_changes.clear()
|
|
181
|
+
|
|
182
|
+
logger.info(f"Processing {len(changes)} file changes")
|
|
183
|
+
|
|
184
|
+
# Trigger callback
|
|
185
|
+
if self._on_change:
|
|
186
|
+
try:
|
|
187
|
+
self._on_change(changes)
|
|
188
|
+
except Exception as e:
|
|
189
|
+
logger.error(f"Error in change callback: {e}")
|
|
190
|
+
|
|
191
|
+
except asyncio.CancelledError:
|
|
192
|
+
# Debounce was reset, ignore
|
|
193
|
+
pass
|
|
194
|
+
except Exception as e:
|
|
195
|
+
logger.error(f"Error in debounced callback: {e}")
|
|
196
|
+
|
|
197
|
+
def stop(self) -> None:
|
|
198
|
+
"""Stop file monitoring."""
|
|
199
|
+
if not self._running:
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
if self._observer:
|
|
203
|
+
self._observer.stop()
|
|
204
|
+
self._observer.join(timeout=5)
|
|
205
|
+
self._observer = None
|
|
206
|
+
|
|
207
|
+
if self._debounce_task and not self._debounce_task.done():
|
|
208
|
+
self._debounce_task.cancel()
|
|
209
|
+
|
|
210
|
+
self._running = False
|
|
211
|
+
self._pending_changes.clear()
|
|
212
|
+
|
|
213
|
+
logger.info("Watchdog stopped")
|
|
214
|
+
|
|
215
|
+
@property
|
|
216
|
+
def is_running(self) -> bool:
|
|
217
|
+
"""Check if watchdog is currently running."""
|
|
218
|
+
return self._running
|
|
219
|
+
|
|
220
|
+
@property
|
|
221
|
+
def pending_count(self) -> int:
|
|
222
|
+
"""Get number of pending changes."""
|
|
223
|
+
with self._lock:
|
|
224
|
+
return len(self._pending_changes)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class SimpleWatchdog:
|
|
228
|
+
"""
|
|
229
|
+
Simplified watchdog for polling-based monitoring.
|
|
230
|
+
|
|
231
|
+
Use this when the watchdog library is not available
|
|
232
|
+
or for environments where filesystem events are unreliable.
|
|
233
|
+
"""
|
|
234
|
+
|
|
235
|
+
def __init__(
|
|
236
|
+
self,
|
|
237
|
+
patterns: list[str] = None,
|
|
238
|
+
check_interval: float = 10.0,
|
|
239
|
+
on_change_callback: Optional[Callable[[Set[Path]], None]] = None,
|
|
240
|
+
):
|
|
241
|
+
self._patterns = patterns or ["*.md", "*.py", "*.txt"]
|
|
242
|
+
self._check_interval = check_interval
|
|
243
|
+
self._on_change = on_change_callback
|
|
244
|
+
self._running = False
|
|
245
|
+
self._file_mtimes: dict[Path, float] = {}
|
|
246
|
+
self._check_task: Optional[asyncio.Task] = None
|
|
247
|
+
|
|
248
|
+
async def start(self, watch_path: Path) -> bool:
|
|
249
|
+
"""Start polling-based monitoring."""
|
|
250
|
+
if self._running:
|
|
251
|
+
return True
|
|
252
|
+
|
|
253
|
+
if not watch_path.exists():
|
|
254
|
+
logger.warning(f"Watch path does not exist: {watch_path}")
|
|
255
|
+
return False
|
|
256
|
+
|
|
257
|
+
self._running = True
|
|
258
|
+
self._check_task = asyncio.create_task(self._poll_loop(watch_path))
|
|
259
|
+
|
|
260
|
+
logger.info(f"SimpleWatchdog started polling: {watch_path}")
|
|
261
|
+
return True
|
|
262
|
+
|
|
263
|
+
async def _poll_loop(self, watch_path: Path) -> None:
|
|
264
|
+
"""Main polling loop."""
|
|
265
|
+
# Initial scan
|
|
266
|
+
self._scan_directory(watch_path)
|
|
267
|
+
|
|
268
|
+
while self._running:
|
|
269
|
+
try:
|
|
270
|
+
await asyncio.sleep(self._check_interval)
|
|
271
|
+
|
|
272
|
+
if not self._running:
|
|
273
|
+
break
|
|
274
|
+
|
|
275
|
+
# Check for changes
|
|
276
|
+
changes = self._check_changes(watch_path)
|
|
277
|
+
|
|
278
|
+
if changes and self._on_change:
|
|
279
|
+
try:
|
|
280
|
+
self._on_change(changes)
|
|
281
|
+
except Exception as e:
|
|
282
|
+
logger.error(f"Error in change callback: {e}")
|
|
283
|
+
|
|
284
|
+
except asyncio.CancelledError:
|
|
285
|
+
break
|
|
286
|
+
except Exception as e:
|
|
287
|
+
logger.error(f"Error in poll loop: {e}")
|
|
288
|
+
|
|
289
|
+
def _scan_directory(self, watch_path: Path) -> None:
|
|
290
|
+
"""Scan directory and record file mtimes."""
|
|
291
|
+
for pattern in self._patterns:
|
|
292
|
+
for file_path in watch_path.rglob(pattern.lstrip("*")):
|
|
293
|
+
if file_path.is_file():
|
|
294
|
+
try:
|
|
295
|
+
self._file_mtimes[file_path] = file_path.stat().st_mtime
|
|
296
|
+
except OSError:
|
|
297
|
+
pass
|
|
298
|
+
|
|
299
|
+
def _check_changes(self, watch_path: Path) -> Set[Path]:
|
|
300
|
+
"""Check for file changes since last scan."""
|
|
301
|
+
changes = set()
|
|
302
|
+
current_files: Set[Path] = set()
|
|
303
|
+
|
|
304
|
+
# Check existing files
|
|
305
|
+
for pattern in self._patterns:
|
|
306
|
+
suffix = pattern.lstrip("*")
|
|
307
|
+
for file_path in watch_path.rglob(f"*{suffix}"):
|
|
308
|
+
if not file_path.is_file():
|
|
309
|
+
continue
|
|
310
|
+
|
|
311
|
+
current_files.add(file_path)
|
|
312
|
+
|
|
313
|
+
try:
|
|
314
|
+
mtime = file_path.stat().st_mtime
|
|
315
|
+
except OSError:
|
|
316
|
+
continue
|
|
317
|
+
|
|
318
|
+
# Check if new or modified
|
|
319
|
+
if file_path not in self._file_mtimes:
|
|
320
|
+
changes.add(file_path)
|
|
321
|
+
self._file_mtimes[file_path] = mtime
|
|
322
|
+
elif self._file_mtimes[file_path] != mtime:
|
|
323
|
+
changes.add(file_path)
|
|
324
|
+
self._file_mtimes[file_path] = mtime
|
|
325
|
+
|
|
326
|
+
# Check for deleted files
|
|
327
|
+
deleted = set(self._file_mtimes.keys()) - current_files
|
|
328
|
+
for file_path in deleted:
|
|
329
|
+
del self._file_mtimes[file_path]
|
|
330
|
+
# Note: We don't add deleted files to changes
|
|
331
|
+
# The RAG system should handle missing files gracefully
|
|
332
|
+
|
|
333
|
+
return changes
|
|
334
|
+
|
|
335
|
+
async def stop(self) -> None:
|
|
336
|
+
"""Stop polling."""
|
|
337
|
+
self._running = False
|
|
338
|
+
|
|
339
|
+
if self._check_task and not self._check_task.done():
|
|
340
|
+
self._check_task.cancel()
|
|
341
|
+
try:
|
|
342
|
+
await self._check_task
|
|
343
|
+
except asyncio.CancelledError:
|
|
344
|
+
pass
|
|
345
|
+
|
|
346
|
+
self._file_mtimes.clear()
|
|
347
|
+
logger.info("SimpleWatchdog stopped")
|
|
348
|
+
|
|
349
|
+
@property
|
|
350
|
+
def is_running(self) -> bool:
|
|
351
|
+
"""Check if watchdog is currently running."""
|
|
352
|
+
return self._running
|
agent_server/main.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""
|
|
2
|
+
HDSP Agent Server - FastAPI Entry Point
|
|
3
|
+
|
|
4
|
+
AI Agent Server for IDE integrations (JupyterLab, VS Code, PyCharm)
|
|
5
|
+
|
|
6
|
+
This server always runs in embedded mode (HDSP_AGENT_MODE=embedded)
|
|
7
|
+
since it's the actual implementation server that executes agent logic.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
from contextlib import asynccontextmanager
|
|
13
|
+
|
|
14
|
+
from fastapi import FastAPI
|
|
15
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
16
|
+
|
|
17
|
+
from agent_server.routers import agent, chat, config, file_resolver, health, rag
|
|
18
|
+
|
|
19
|
+
# Configure logging
|
|
20
|
+
logging.basicConfig(
|
|
21
|
+
level=logging.INFO,
|
|
22
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
23
|
+
)
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@asynccontextmanager
|
|
28
|
+
async def lifespan(app: FastAPI):
|
|
29
|
+
"""Application lifespan handler for startup/shutdown events"""
|
|
30
|
+
# Startup
|
|
31
|
+
logger.info("Starting HDSP Agent Server...")
|
|
32
|
+
|
|
33
|
+
# Force embedded mode for the server (it IS the implementation)
|
|
34
|
+
os.environ.setdefault("HDSP_AGENT_MODE", "embedded")
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
from hdsp_agent_core.factory import get_service_factory
|
|
38
|
+
|
|
39
|
+
factory = get_service_factory()
|
|
40
|
+
await factory.initialize()
|
|
41
|
+
|
|
42
|
+
logger.info(f"ServiceFactory initialized in {factory.mode.value} mode")
|
|
43
|
+
|
|
44
|
+
# Log service status
|
|
45
|
+
rag_service = factory.get_rag_service()
|
|
46
|
+
if rag_service.is_ready():
|
|
47
|
+
logger.info("RAG service ready")
|
|
48
|
+
else:
|
|
49
|
+
logger.info("RAG service not ready (will start without RAG)")
|
|
50
|
+
|
|
51
|
+
except ImportError as e:
|
|
52
|
+
logger.warning(f"hdsp_agent_core not available: {e}")
|
|
53
|
+
# Fallback to legacy initialization
|
|
54
|
+
await _legacy_startup()
|
|
55
|
+
except Exception as e:
|
|
56
|
+
logger.warning(f"Failed to initialize ServiceFactory: {e}")
|
|
57
|
+
# Fallback to legacy initialization
|
|
58
|
+
await _legacy_startup()
|
|
59
|
+
|
|
60
|
+
yield
|
|
61
|
+
|
|
62
|
+
# Shutdown
|
|
63
|
+
logger.info("Shutting down HDSP Agent Server...")
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
from hdsp_agent_core.factory import get_service_factory
|
|
67
|
+
|
|
68
|
+
factory = get_service_factory()
|
|
69
|
+
await factory.shutdown()
|
|
70
|
+
logger.info("ServiceFactory shutdown complete")
|
|
71
|
+
except Exception as e:
|
|
72
|
+
logger.warning(f"Error during ServiceFactory shutdown: {e}")
|
|
73
|
+
# Fallback to legacy shutdown
|
|
74
|
+
await _legacy_shutdown()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
async def _legacy_startup():
|
|
78
|
+
"""Legacy startup for backward compatibility (when hdsp_agent_core not available)"""
|
|
79
|
+
logger.info("Using legacy startup (hdsp_agent_core not available)")
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
from hdsp_agent_core.managers.config_manager import ConfigManager
|
|
83
|
+
|
|
84
|
+
ConfigManager.get_instance()
|
|
85
|
+
logger.info("Configuration loaded successfully")
|
|
86
|
+
except Exception as e:
|
|
87
|
+
logger.warning(f"Failed to load configuration: {e}")
|
|
88
|
+
|
|
89
|
+
# Initialize RAG system
|
|
90
|
+
try:
|
|
91
|
+
from hdsp_agent_core.models.rag import get_default_rag_config
|
|
92
|
+
|
|
93
|
+
from agent_server.core.rag_manager import get_rag_manager
|
|
94
|
+
|
|
95
|
+
rag_config = get_default_rag_config()
|
|
96
|
+
if rag_config.is_enabled():
|
|
97
|
+
rag_manager = get_rag_manager(rag_config)
|
|
98
|
+
await rag_manager.initialize()
|
|
99
|
+
logger.info("RAG system initialized successfully")
|
|
100
|
+
else:
|
|
101
|
+
logger.info("RAG system disabled by configuration")
|
|
102
|
+
except Exception as e:
|
|
103
|
+
logger.warning(f"Failed to initialize RAG system: {e}")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
async def _legacy_shutdown():
|
|
107
|
+
"""Legacy shutdown for backward compatibility"""
|
|
108
|
+
logger.info("Using legacy shutdown")
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
from agent_server.core.rag_manager import get_rag_manager, reset_rag_manager
|
|
112
|
+
|
|
113
|
+
rag_manager = get_rag_manager()
|
|
114
|
+
if rag_manager.is_ready:
|
|
115
|
+
await rag_manager.shutdown()
|
|
116
|
+
logger.info("RAG system shut down successfully")
|
|
117
|
+
reset_rag_manager()
|
|
118
|
+
except Exception as e:
|
|
119
|
+
logger.warning(f"Error during legacy RAG shutdown: {e}")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
app = FastAPI(
|
|
123
|
+
title="HDSP Agent Server",
|
|
124
|
+
description="AI Agent Server for IDE integrations - provides intelligent code assistance",
|
|
125
|
+
version="1.0.0",
|
|
126
|
+
lifespan=lifespan,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# CORS middleware for cross-origin requests from IDE extensions
|
|
130
|
+
app.add_middleware(
|
|
131
|
+
CORSMiddleware,
|
|
132
|
+
allow_origins=["*"], # Development: allow all. Production: restrict
|
|
133
|
+
allow_credentials=True,
|
|
134
|
+
allow_methods=["*"],
|
|
135
|
+
allow_headers=["*"],
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# Register routers
|
|
139
|
+
app.include_router(health.router, tags=["Health"])
|
|
140
|
+
app.include_router(config.router, prefix="/config", tags=["Configuration"])
|
|
141
|
+
app.include_router(agent.router, prefix="/agent", tags=["Agent"])
|
|
142
|
+
app.include_router(chat.router, prefix="/chat", tags=["Chat"])
|
|
143
|
+
app.include_router(rag.router, prefix="/rag", tags=["RAG"])
|
|
144
|
+
app.include_router(file_resolver.router, prefix="/file", tags=["File Resolution"])
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def run():
|
|
148
|
+
"""Entry point for `hdsp-agent-server` CLI command"""
|
|
149
|
+
import uvicorn
|
|
150
|
+
|
|
151
|
+
uvicorn.run(
|
|
152
|
+
"agent_server.main:app",
|
|
153
|
+
host="0.0.0.0",
|
|
154
|
+
port=8000,
|
|
155
|
+
reload=True,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
if __name__ == "__main__":
|
|
160
|
+
run()
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""
|
|
2
|
+
HDSP Agent Prompts Module
|
|
3
|
+
|
|
4
|
+
Re-export from hdsp_agent_core.prompts for backward compatibility.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# Auto-Agent Prompts
|
|
8
|
+
from hdsp_agent_core.prompts.auto_agent_prompts import (
|
|
9
|
+
ADAPTIVE_REPLAN_PROMPT,
|
|
10
|
+
CODE_GENERATION_PROMPT,
|
|
11
|
+
ERROR_REFINEMENT_PROMPT,
|
|
12
|
+
FINAL_ANSWER_PROMPT,
|
|
13
|
+
PLAN_GENERATION_PROMPT,
|
|
14
|
+
REFLECTION_PROMPT,
|
|
15
|
+
STRUCTURED_PLAN_PROMPT,
|
|
16
|
+
format_final_answer_prompt,
|
|
17
|
+
format_plan_prompt,
|
|
18
|
+
format_refine_prompt,
|
|
19
|
+
format_reflection_prompt,
|
|
20
|
+
format_replan_prompt,
|
|
21
|
+
format_structured_plan_prompt,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# Cell Action Prompts
|
|
25
|
+
from hdsp_agent_core.prompts.cell_action_prompts import (
|
|
26
|
+
CUSTOM_REQUEST_PROMPT,
|
|
27
|
+
DEFAULT_SYSTEM_PROMPT,
|
|
28
|
+
EXPLAIN_CODE_PROMPT,
|
|
29
|
+
FIX_CODE_PROMPT,
|
|
30
|
+
format_chat_prompt,
|
|
31
|
+
format_custom_prompt,
|
|
32
|
+
format_explain_prompt,
|
|
33
|
+
format_fix_prompt,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
# Auto-Agent Prompts
|
|
38
|
+
"PLAN_GENERATION_PROMPT",
|
|
39
|
+
"CODE_GENERATION_PROMPT",
|
|
40
|
+
"ERROR_REFINEMENT_PROMPT",
|
|
41
|
+
"ADAPTIVE_REPLAN_PROMPT",
|
|
42
|
+
"FINAL_ANSWER_PROMPT",
|
|
43
|
+
"STRUCTURED_PLAN_PROMPT",
|
|
44
|
+
"REFLECTION_PROMPT",
|
|
45
|
+
"format_plan_prompt",
|
|
46
|
+
"format_refine_prompt",
|
|
47
|
+
"format_final_answer_prompt",
|
|
48
|
+
"format_replan_prompt",
|
|
49
|
+
"format_structured_plan_prompt",
|
|
50
|
+
"format_reflection_prompt",
|
|
51
|
+
# Cell Action Prompts
|
|
52
|
+
"EXPLAIN_CODE_PROMPT",
|
|
53
|
+
"FIX_CODE_PROMPT",
|
|
54
|
+
"CUSTOM_REQUEST_PROMPT",
|
|
55
|
+
"DEFAULT_SYSTEM_PROMPT",
|
|
56
|
+
"format_explain_prompt",
|
|
57
|
+
"format_fix_prompt",
|
|
58
|
+
"format_custom_prompt",
|
|
59
|
+
"format_chat_prompt",
|
|
60
|
+
]
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""
|
|
2
|
+
File Action Prompts
|
|
3
|
+
Python 파일 분석 및 에러 수정용 LLM 프롬프트
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def format_file_fix_prompt(
|
|
8
|
+
main_file: dict, error_output: str, related_files: list = None
|
|
9
|
+
) -> str:
|
|
10
|
+
"""Python 파일 에러 수정용 프롬프트 생성
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
main_file: { 'path': str, 'content': str }
|
|
14
|
+
error_output: 에러 메시지/트레이스백
|
|
15
|
+
related_files: [{ 'path': str, 'content': str }]
|
|
16
|
+
"""
|
|
17
|
+
related_context = ""
|
|
18
|
+
if related_files:
|
|
19
|
+
related_context = "\n## 관련 파일들\n"
|
|
20
|
+
for rf in related_files:
|
|
21
|
+
if rf.get("content"):
|
|
22
|
+
related_context += f"""
|
|
23
|
+
### {rf['path']}
|
|
24
|
+
```python
|
|
25
|
+
{rf['content']}
|
|
26
|
+
```
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
return f"""Python 파일에서 에러가 발생했습니다. 에러를 분석하고 수정된 코드를 제공하세요.
|
|
30
|
+
|
|
31
|
+
## 에러 메시지
|
|
32
|
+
```
|
|
33
|
+
{error_output}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## 메인 파일: {main_file['path']}
|
|
37
|
+
```python
|
|
38
|
+
{main_file['content']}
|
|
39
|
+
```
|
|
40
|
+
{related_context}
|
|
41
|
+
|
|
42
|
+
## 지침
|
|
43
|
+
1. 에러의 근본 원인을 분석하세요
|
|
44
|
+
2. 수정이 필요한 파일의 **전체 코드**를 제공하세요 (부분 수정 아님)
|
|
45
|
+
3. 여러 파일 수정이 필요하면 각각 제공하세요
|
|
46
|
+
4. 수정 사항을 간단히 설명하세요
|
|
47
|
+
5. **코드 내 주석과 문자열은 한글 또는 영어로만 작성하세요 (한자 사용 절대 금지)**
|
|
48
|
+
|
|
49
|
+
## 출력 형식
|
|
50
|
+
### 에러 원인
|
|
51
|
+
(에러가 발생한 원인 설명)
|
|
52
|
+
|
|
53
|
+
### 수정 파일: [파일경로]
|
|
54
|
+
```python
|
|
55
|
+
[전체 수정된 코드]
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 추가 수정 파일: [파일경로] (필요한 경우)
|
|
59
|
+
```python
|
|
60
|
+
[전체 수정된 코드]
|
|
61
|
+
```
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def format_file_explain_prompt(file_path: str, file_content: str) -> str:
|
|
66
|
+
"""Python 파일 설명용 프롬프트 생성"""
|
|
67
|
+
return f"""다음 Python 파일의 코드를 자세히 설명해주세요.
|
|
68
|
+
|
|
69
|
+
## 파일: {file_path}
|
|
70
|
+
```python
|
|
71
|
+
{file_content}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## 다음 형식으로 응답해주세요
|
|
75
|
+
|
|
76
|
+
### 파일 개요
|
|
77
|
+
(이 파일의 전체적인 목적과 기능)
|
|
78
|
+
|
|
79
|
+
### 주요 구성 요소
|
|
80
|
+
(클래스, 함수, 변수 등의 설명)
|
|
81
|
+
|
|
82
|
+
### 코드 흐름
|
|
83
|
+
(코드의 실행 흐름 설명)
|
|
84
|
+
|
|
85
|
+
### 의존성
|
|
86
|
+
(import하는 모듈과 그 용도)
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def format_file_custom_prompt(
|
|
91
|
+
main_file: dict, custom_prompt: str, related_files: list = None
|
|
92
|
+
) -> str:
|
|
93
|
+
"""커스텀 질문용 프롬프트 생성"""
|
|
94
|
+
related_context = ""
|
|
95
|
+
if related_files:
|
|
96
|
+
related_context = "\n## 관련 파일들\n"
|
|
97
|
+
for rf in related_files:
|
|
98
|
+
if rf.get("content"):
|
|
99
|
+
related_context += f"""
|
|
100
|
+
### {rf['path']}
|
|
101
|
+
```python
|
|
102
|
+
{rf['content']}
|
|
103
|
+
```
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
return f"""{custom_prompt}
|
|
107
|
+
|
|
108
|
+
## 메인 파일: {main_file['path']}
|
|
109
|
+
```python
|
|
110
|
+
{main_file['content']}
|
|
111
|
+
```
|
|
112
|
+
{related_context}
|
|
113
|
+
"""
|