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
jupyter_ext/handlers.py
ADDED
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
"""
|
|
2
|
+
HDSP Jupyter Extension Handlers.
|
|
3
|
+
|
|
4
|
+
ServiceFactory-based handlers supporting both embedded and proxy modes:
|
|
5
|
+
- Embedded mode (HDSP_AGENT_MODE=embedded): Direct in-process execution
|
|
6
|
+
- Proxy mode (HDSP_AGENT_MODE=proxy): HTTP proxy to external Agent Server
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
from typing import Any, Dict
|
|
13
|
+
|
|
14
|
+
import httpx
|
|
15
|
+
from jupyter_server.base.handlers import APIHandler
|
|
16
|
+
from jupyter_server.utils import url_path_join
|
|
17
|
+
from tornado.web import RequestHandler
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _get_service_factory():
|
|
23
|
+
"""Get ServiceFactory instance (lazy import to avoid circular imports)"""
|
|
24
|
+
from hdsp_agent_core.factory import get_service_factory
|
|
25
|
+
return get_service_factory()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _is_embedded_mode() -> bool:
|
|
29
|
+
"""Check if running in embedded mode"""
|
|
30
|
+
try:
|
|
31
|
+
factory = _get_service_factory()
|
|
32
|
+
return factory.is_embedded
|
|
33
|
+
except Exception:
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# ============ Service-Based Handlers ============
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class AgentPlanHandler(APIHandler):
|
|
41
|
+
"""Handler for /agent/plan endpoint using ServiceFactory."""
|
|
42
|
+
|
|
43
|
+
async def post(self):
|
|
44
|
+
"""Generate execution plan."""
|
|
45
|
+
try:
|
|
46
|
+
from hdsp_agent_core.models.agent import PlanRequest
|
|
47
|
+
|
|
48
|
+
factory = _get_service_factory()
|
|
49
|
+
agent_service = factory.get_agent_service()
|
|
50
|
+
|
|
51
|
+
# Parse request
|
|
52
|
+
body = json.loads(self.request.body.decode("utf-8"))
|
|
53
|
+
request = PlanRequest(**body)
|
|
54
|
+
|
|
55
|
+
# Call service
|
|
56
|
+
response = await agent_service.generate_plan(request)
|
|
57
|
+
|
|
58
|
+
self.set_header("Content-Type", "application/json")
|
|
59
|
+
self.write(response.model_dump(mode="json"))
|
|
60
|
+
|
|
61
|
+
except Exception as e:
|
|
62
|
+
logger.error(f"Plan generation failed: {e}", exc_info=True)
|
|
63
|
+
self.set_status(500)
|
|
64
|
+
self.write({"error": str(e)})
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class AgentRefineHandler(APIHandler):
|
|
68
|
+
"""Handler for /agent/refine endpoint using ServiceFactory."""
|
|
69
|
+
|
|
70
|
+
async def post(self):
|
|
71
|
+
"""Refine code after error."""
|
|
72
|
+
try:
|
|
73
|
+
from hdsp_agent_core.models.agent import RefineRequest
|
|
74
|
+
|
|
75
|
+
factory = _get_service_factory()
|
|
76
|
+
agent_service = factory.get_agent_service()
|
|
77
|
+
|
|
78
|
+
body = json.loads(self.request.body.decode("utf-8"))
|
|
79
|
+
request = RefineRequest(**body)
|
|
80
|
+
|
|
81
|
+
response = await agent_service.refine_code(request)
|
|
82
|
+
|
|
83
|
+
self.set_header("Content-Type", "application/json")
|
|
84
|
+
self.write(response.model_dump(mode="json"))
|
|
85
|
+
|
|
86
|
+
except Exception as e:
|
|
87
|
+
logger.error(f"Refine failed: {e}", exc_info=True)
|
|
88
|
+
self.set_status(500)
|
|
89
|
+
self.write({"error": str(e)})
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class AgentReplanHandler(APIHandler):
|
|
93
|
+
"""Handler for /agent/replan endpoint using ServiceFactory."""
|
|
94
|
+
|
|
95
|
+
async def post(self):
|
|
96
|
+
"""Determine how to handle failed step."""
|
|
97
|
+
try:
|
|
98
|
+
from hdsp_agent_core.models.agent import ReplanRequest
|
|
99
|
+
|
|
100
|
+
factory = _get_service_factory()
|
|
101
|
+
agent_service = factory.get_agent_service()
|
|
102
|
+
|
|
103
|
+
body = json.loads(self.request.body.decode("utf-8"))
|
|
104
|
+
request = ReplanRequest(**body)
|
|
105
|
+
|
|
106
|
+
response = await agent_service.replan(request)
|
|
107
|
+
|
|
108
|
+
self.set_header("Content-Type", "application/json")
|
|
109
|
+
self.write(response.model_dump(mode="json"))
|
|
110
|
+
|
|
111
|
+
except Exception as e:
|
|
112
|
+
logger.error(f"Replan failed: {e}", exc_info=True)
|
|
113
|
+
self.set_status(500)
|
|
114
|
+
self.write({"error": str(e)})
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class AgentValidateHandler(APIHandler):
|
|
118
|
+
"""Handler for /agent/validate endpoint using ServiceFactory."""
|
|
119
|
+
|
|
120
|
+
async def post(self):
|
|
121
|
+
"""Validate code before execution."""
|
|
122
|
+
try:
|
|
123
|
+
factory = _get_service_factory()
|
|
124
|
+
agent_service = factory.get_agent_service()
|
|
125
|
+
|
|
126
|
+
body = json.loads(self.request.body.decode("utf-8"))
|
|
127
|
+
code = body.get("code", "")
|
|
128
|
+
notebook_context = body.get("notebookContext")
|
|
129
|
+
|
|
130
|
+
response = await agent_service.validate_code(code, notebook_context)
|
|
131
|
+
|
|
132
|
+
self.set_header("Content-Type", "application/json")
|
|
133
|
+
self.write(response)
|
|
134
|
+
|
|
135
|
+
except Exception as e:
|
|
136
|
+
logger.error(f"Validate failed: {e}", exc_info=True)
|
|
137
|
+
self.set_status(500)
|
|
138
|
+
self.write({"error": str(e)})
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class ChatMessageHandler(APIHandler):
|
|
142
|
+
"""Handler for /chat/message endpoint using ServiceFactory."""
|
|
143
|
+
|
|
144
|
+
async def post(self):
|
|
145
|
+
"""Send chat message and get response."""
|
|
146
|
+
try:
|
|
147
|
+
from hdsp_agent_core.models.chat import ChatRequest
|
|
148
|
+
|
|
149
|
+
factory = _get_service_factory()
|
|
150
|
+
chat_service = factory.get_chat_service()
|
|
151
|
+
|
|
152
|
+
body = json.loads(self.request.body.decode("utf-8"))
|
|
153
|
+
request = ChatRequest(**body)
|
|
154
|
+
|
|
155
|
+
response = await chat_service.send_message(request)
|
|
156
|
+
|
|
157
|
+
self.set_header("Content-Type", "application/json")
|
|
158
|
+
self.write(response.model_dump(mode="json"))
|
|
159
|
+
|
|
160
|
+
except Exception as e:
|
|
161
|
+
logger.error(f"Chat message failed: {e}", exc_info=True)
|
|
162
|
+
self.set_status(500)
|
|
163
|
+
self.write({"error": str(e)})
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class ChatStreamHandler(APIHandler):
|
|
167
|
+
"""Handler for /chat/stream endpoint using ServiceFactory."""
|
|
168
|
+
|
|
169
|
+
async def post(self):
|
|
170
|
+
"""Send chat message and get streaming response."""
|
|
171
|
+
try:
|
|
172
|
+
from hdsp_agent_core.models.chat import ChatRequest
|
|
173
|
+
|
|
174
|
+
factory = _get_service_factory()
|
|
175
|
+
chat_service = factory.get_chat_service()
|
|
176
|
+
|
|
177
|
+
body = json.loads(self.request.body.decode("utf-8"))
|
|
178
|
+
request = ChatRequest(**body)
|
|
179
|
+
|
|
180
|
+
# Set SSE headers
|
|
181
|
+
self.set_header("Content-Type", "text/event-stream")
|
|
182
|
+
self.set_header("Cache-Control", "no-cache")
|
|
183
|
+
self.set_header("Connection", "keep-alive")
|
|
184
|
+
self.set_header("X-Accel-Buffering", "no")
|
|
185
|
+
|
|
186
|
+
async for chunk in chat_service.send_message_stream(request):
|
|
187
|
+
self.write(f"data: {json.dumps(chunk)}\n\n")
|
|
188
|
+
await self.flush()
|
|
189
|
+
|
|
190
|
+
self.finish()
|
|
191
|
+
|
|
192
|
+
except Exception as e:
|
|
193
|
+
logger.error(f"Chat stream failed: {e}", exc_info=True)
|
|
194
|
+
self.write(f'data: {json.dumps({"error": str(e)})}\n\n')
|
|
195
|
+
self.finish()
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class RAGSearchHandler(APIHandler):
|
|
199
|
+
"""Handler for /rag/search endpoint using ServiceFactory."""
|
|
200
|
+
|
|
201
|
+
async def post(self):
|
|
202
|
+
"""Search knowledge base."""
|
|
203
|
+
try:
|
|
204
|
+
from hdsp_agent_core.models.rag import SearchRequest
|
|
205
|
+
|
|
206
|
+
factory = _get_service_factory()
|
|
207
|
+
rag_service = factory.get_rag_service()
|
|
208
|
+
|
|
209
|
+
body = json.loads(self.request.body.decode("utf-8"))
|
|
210
|
+
request = SearchRequest(**body)
|
|
211
|
+
|
|
212
|
+
response = await rag_service.search(request)
|
|
213
|
+
|
|
214
|
+
self.set_header("Content-Type", "application/json")
|
|
215
|
+
self.write(response.model_dump(mode="json"))
|
|
216
|
+
|
|
217
|
+
except Exception as e:
|
|
218
|
+
logger.error(f"RAG search failed: {e}", exc_info=True)
|
|
219
|
+
self.set_status(500)
|
|
220
|
+
self.write({"error": str(e)})
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class RAGStatusHandler(APIHandler):
|
|
224
|
+
"""Handler for /rag/status endpoint using ServiceFactory."""
|
|
225
|
+
|
|
226
|
+
async def get(self):
|
|
227
|
+
"""Get RAG system status."""
|
|
228
|
+
try:
|
|
229
|
+
factory = _get_service_factory()
|
|
230
|
+
rag_service = factory.get_rag_service()
|
|
231
|
+
|
|
232
|
+
status = await rag_service.get_index_status()
|
|
233
|
+
|
|
234
|
+
self.set_header("Content-Type", "application/json")
|
|
235
|
+
self.write(status)
|
|
236
|
+
|
|
237
|
+
except Exception as e:
|
|
238
|
+
logger.error(f"RAG status failed: {e}", exc_info=True)
|
|
239
|
+
self.set_status(500)
|
|
240
|
+
self.write({"error": str(e)})
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
# ============ Proxy-Only Handlers (for endpoints not yet migrated) ============
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class BaseProxyHandler(APIHandler):
|
|
247
|
+
"""Base handler that proxies requests to Agent Server."""
|
|
248
|
+
|
|
249
|
+
@property
|
|
250
|
+
def agent_server_url(self) -> str:
|
|
251
|
+
"""Get the Agent Server base URL."""
|
|
252
|
+
from .config import get_agent_server_config
|
|
253
|
+
config = get_agent_server_config()
|
|
254
|
+
return config.base_url
|
|
255
|
+
|
|
256
|
+
@property
|
|
257
|
+
def timeout(self) -> float:
|
|
258
|
+
"""Get request timeout."""
|
|
259
|
+
from .config import get_agent_server_config
|
|
260
|
+
config = get_agent_server_config()
|
|
261
|
+
return config.timeout
|
|
262
|
+
|
|
263
|
+
def get_proxy_path(self) -> str:
|
|
264
|
+
"""Get the path to proxy to (override in subclasses if needed)."""
|
|
265
|
+
request_path = self.request.path
|
|
266
|
+
base_url = self.settings.get("base_url", "/")
|
|
267
|
+
prefix = url_path_join(base_url, "hdsp-agent")
|
|
268
|
+
if request_path.startswith(prefix):
|
|
269
|
+
return request_path[len(prefix):]
|
|
270
|
+
return request_path
|
|
271
|
+
|
|
272
|
+
async def proxy_request(self, method: str = "GET", body: bytes = None):
|
|
273
|
+
"""Proxy the request to Agent Server."""
|
|
274
|
+
target_path = self.get_proxy_path()
|
|
275
|
+
target_url = f"{self.agent_server_url}{target_path}"
|
|
276
|
+
|
|
277
|
+
headers = {}
|
|
278
|
+
for name, value in self.request.headers.items():
|
|
279
|
+
if name.lower() not in ("host", "content-length"):
|
|
280
|
+
headers[name] = value
|
|
281
|
+
headers["Content-Type"] = "application/json"
|
|
282
|
+
|
|
283
|
+
try:
|
|
284
|
+
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
|
285
|
+
if method == "GET":
|
|
286
|
+
response = await client.get(target_url, headers=headers)
|
|
287
|
+
elif method == "POST":
|
|
288
|
+
response = await client.post(target_url, headers=headers, content=body)
|
|
289
|
+
elif method == "PUT":
|
|
290
|
+
response = await client.put(target_url, headers=headers, content=body)
|
|
291
|
+
elif method == "DELETE":
|
|
292
|
+
response = await client.delete(target_url, headers=headers)
|
|
293
|
+
else:
|
|
294
|
+
self.set_status(405)
|
|
295
|
+
self.write({"error": f"Method {method} not supported"})
|
|
296
|
+
return
|
|
297
|
+
|
|
298
|
+
self.set_status(response.status_code)
|
|
299
|
+
for name, value in response.headers.items():
|
|
300
|
+
if name.lower() not in ("content-encoding", "transfer-encoding", "content-length"):
|
|
301
|
+
self.set_header(name, value)
|
|
302
|
+
self.write(response.content)
|
|
303
|
+
|
|
304
|
+
except httpx.ConnectError:
|
|
305
|
+
self.set_status(503)
|
|
306
|
+
self.write({
|
|
307
|
+
"error": "Agent Server is not available",
|
|
308
|
+
"detail": f"Could not connect to {self.agent_server_url}",
|
|
309
|
+
})
|
|
310
|
+
except httpx.TimeoutException:
|
|
311
|
+
self.set_status(504)
|
|
312
|
+
self.write({
|
|
313
|
+
"error": "Agent Server timeout",
|
|
314
|
+
"detail": f"Request to {target_url} timed out after {self.timeout}s",
|
|
315
|
+
})
|
|
316
|
+
except Exception as e:
|
|
317
|
+
self.set_status(500)
|
|
318
|
+
self.write({"error": "Proxy error", "detail": str(e)})
|
|
319
|
+
|
|
320
|
+
async def get(self, *args, **kwargs):
|
|
321
|
+
await self.proxy_request("GET")
|
|
322
|
+
|
|
323
|
+
async def post(self, *args, **kwargs):
|
|
324
|
+
await self.proxy_request("POST", self.request.body)
|
|
325
|
+
|
|
326
|
+
async def put(self, *args, **kwargs):
|
|
327
|
+
await self.proxy_request("PUT", self.request.body)
|
|
328
|
+
|
|
329
|
+
async def delete(self, *args, **kwargs):
|
|
330
|
+
await self.proxy_request("DELETE")
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
class StreamProxyHandler(APIHandler):
|
|
334
|
+
"""Handler for streaming proxy requests (SSE)."""
|
|
335
|
+
|
|
336
|
+
@property
|
|
337
|
+
def agent_server_url(self) -> str:
|
|
338
|
+
from .config import get_agent_server_config
|
|
339
|
+
config = get_agent_server_config()
|
|
340
|
+
return config.base_url
|
|
341
|
+
|
|
342
|
+
@property
|
|
343
|
+
def timeout(self) -> float:
|
|
344
|
+
from .config import get_agent_server_config
|
|
345
|
+
config = get_agent_server_config()
|
|
346
|
+
return config.timeout
|
|
347
|
+
|
|
348
|
+
def get_proxy_path(self) -> str:
|
|
349
|
+
request_path = self.request.path
|
|
350
|
+
base_url = self.settings.get("base_url", "/")
|
|
351
|
+
prefix = url_path_join(base_url, "hdsp-agent")
|
|
352
|
+
if request_path.startswith(prefix):
|
|
353
|
+
return request_path[len(prefix):]
|
|
354
|
+
return request_path
|
|
355
|
+
|
|
356
|
+
async def post(self, *args, **kwargs):
|
|
357
|
+
"""Handle streaming POST requests (SSE)."""
|
|
358
|
+
target_path = self.get_proxy_path()
|
|
359
|
+
target_url = f"{self.agent_server_url}{target_path}"
|
|
360
|
+
|
|
361
|
+
self.set_header("Content-Type", "text/event-stream")
|
|
362
|
+
self.set_header("Cache-Control", "no-cache")
|
|
363
|
+
self.set_header("Connection", "keep-alive")
|
|
364
|
+
self.set_header("X-Accel-Buffering", "no")
|
|
365
|
+
|
|
366
|
+
try:
|
|
367
|
+
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
|
368
|
+
async with client.stream(
|
|
369
|
+
"POST",
|
|
370
|
+
target_url,
|
|
371
|
+
content=self.request.body,
|
|
372
|
+
headers={"Content-Type": "application/json"},
|
|
373
|
+
) as response:
|
|
374
|
+
async for chunk in response.aiter_bytes():
|
|
375
|
+
self.write(chunk)
|
|
376
|
+
await self.flush()
|
|
377
|
+
|
|
378
|
+
except httpx.ConnectError:
|
|
379
|
+
self.write(f'data: {json.dumps({"error": "Agent Server is not available"})}\n\n')
|
|
380
|
+
except httpx.TimeoutException:
|
|
381
|
+
self.write(f'data: {json.dumps({"error": "Agent Server timeout"})}\n\n')
|
|
382
|
+
except Exception as e:
|
|
383
|
+
self.write(f'data: {json.dumps({"error": str(e)})}\n\n')
|
|
384
|
+
finally:
|
|
385
|
+
self.finish()
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
# ============ Health & Config Handlers ============
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
class HealthHandler(APIHandler):
|
|
392
|
+
"""Health check handler."""
|
|
393
|
+
|
|
394
|
+
async def get(self):
|
|
395
|
+
"""Return extension health status."""
|
|
396
|
+
try:
|
|
397
|
+
factory = _get_service_factory()
|
|
398
|
+
mode = factory.mode.value if factory.is_initialized else "not_initialized"
|
|
399
|
+
|
|
400
|
+
status = {
|
|
401
|
+
"status": "healthy",
|
|
402
|
+
"extension_version": "2.0.0",
|
|
403
|
+
"mode": mode,
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if factory.is_embedded:
|
|
407
|
+
# In embedded mode, check RAG service directly
|
|
408
|
+
rag_service = factory.get_rag_service()
|
|
409
|
+
status["rag"] = {
|
|
410
|
+
"ready": rag_service.is_ready(),
|
|
411
|
+
}
|
|
412
|
+
else:
|
|
413
|
+
# In proxy mode, check agent server connectivity
|
|
414
|
+
from .config import get_agent_server_config
|
|
415
|
+
config = get_agent_server_config()
|
|
416
|
+
|
|
417
|
+
agent_server_healthy = False
|
|
418
|
+
agent_server_error = None
|
|
419
|
+
|
|
420
|
+
try:
|
|
421
|
+
async with httpx.AsyncClient(timeout=5.0) as client:
|
|
422
|
+
response = await client.get(f"{config.base_url}/health")
|
|
423
|
+
agent_server_healthy = response.status_code == 200
|
|
424
|
+
except Exception as e:
|
|
425
|
+
agent_server_error = str(e)
|
|
426
|
+
|
|
427
|
+
status["agent_server"] = {
|
|
428
|
+
"url": config.base_url,
|
|
429
|
+
"healthy": agent_server_healthy,
|
|
430
|
+
"error": agent_server_error,
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
self.write(status)
|
|
434
|
+
|
|
435
|
+
except Exception as e:
|
|
436
|
+
logger.error(f"Health check failed: {e}")
|
|
437
|
+
self.write({
|
|
438
|
+
"status": "degraded",
|
|
439
|
+
"error": str(e),
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
class ConfigProxyHandler(BaseProxyHandler):
|
|
444
|
+
"""Proxy handler for /config endpoint."""
|
|
445
|
+
|
|
446
|
+
def get_proxy_path(self) -> str:
|
|
447
|
+
return "/config"
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
# ============ Remaining Proxy Handlers (for endpoints not yet in ServiceFactory) ============
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
class AgentReflectProxyHandler(BaseProxyHandler):
|
|
454
|
+
"""Proxy handler for /agent/reflect endpoint."""
|
|
455
|
+
|
|
456
|
+
def get_proxy_path(self) -> str:
|
|
457
|
+
return "/agent/reflect"
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
class AgentVerifyStateProxyHandler(BaseProxyHandler):
|
|
461
|
+
"""Proxy handler for /agent/verify-state endpoint."""
|
|
462
|
+
|
|
463
|
+
def get_proxy_path(self) -> str:
|
|
464
|
+
return "/agent/verify-state"
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
class AgentPlanStreamProxyHandler(StreamProxyHandler):
|
|
468
|
+
"""Proxy handler for /agent/plan/stream endpoint."""
|
|
469
|
+
|
|
470
|
+
def get_proxy_path(self) -> str:
|
|
471
|
+
return "/agent/plan/stream"
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
class CellActionProxyHandler(BaseProxyHandler):
|
|
475
|
+
"""Proxy handler for /cell/action endpoint."""
|
|
476
|
+
|
|
477
|
+
def get_proxy_path(self) -> str:
|
|
478
|
+
return "/cell/action"
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
class FileActionProxyHandler(BaseProxyHandler):
|
|
482
|
+
"""Proxy handler for /file/action endpoint."""
|
|
483
|
+
|
|
484
|
+
def get_proxy_path(self) -> str:
|
|
485
|
+
return "/file/action"
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
class FileResolveProxyHandler(BaseProxyHandler):
|
|
489
|
+
"""Proxy handler for /file/resolve endpoint."""
|
|
490
|
+
|
|
491
|
+
def get_proxy_path(self) -> str:
|
|
492
|
+
return "/file/resolve"
|
|
493
|
+
|
|
494
|
+
async def post(self, *args, **kwargs):
|
|
495
|
+
"""Handle POST with notebookDir path conversion."""
|
|
496
|
+
try:
|
|
497
|
+
body = json.loads(self.request.body.decode("utf-8"))
|
|
498
|
+
|
|
499
|
+
if "notebookDir" in body and body["notebookDir"]:
|
|
500
|
+
server_root = self.settings.get("server_root_dir", os.getcwd())
|
|
501
|
+
server_root = os.path.expanduser(server_root)
|
|
502
|
+
notebook_dir = body["notebookDir"]
|
|
503
|
+
|
|
504
|
+
if not os.path.isabs(notebook_dir):
|
|
505
|
+
body["notebookDir"] = os.path.join(server_root, notebook_dir)
|
|
506
|
+
|
|
507
|
+
modified_body = json.dumps(body).encode("utf-8")
|
|
508
|
+
await self.proxy_request("POST", modified_body)
|
|
509
|
+
|
|
510
|
+
except Exception as e:
|
|
511
|
+
logger.error(f"FileResolveProxy error: {e}")
|
|
512
|
+
self.set_status(500)
|
|
513
|
+
self.write({"error": f"Failed to process request: {str(e)}"})
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
class FileSelectProxyHandler(BaseProxyHandler):
|
|
517
|
+
"""Proxy handler for /file/select endpoint."""
|
|
518
|
+
|
|
519
|
+
def get_proxy_path(self) -> str:
|
|
520
|
+
return "/file/select"
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
class TaskStatusProxyHandler(BaseProxyHandler):
|
|
524
|
+
"""Proxy handler for /task/{id}/status endpoint."""
|
|
525
|
+
|
|
526
|
+
def get_proxy_path(self) -> str:
|
|
527
|
+
request_path = self.request.path
|
|
528
|
+
parts = request_path.split("/")
|
|
529
|
+
task_idx = parts.index("task") if "task" in parts else -1
|
|
530
|
+
if task_idx >= 0 and task_idx + 1 < len(parts):
|
|
531
|
+
task_id = parts[task_idx + 1]
|
|
532
|
+
return f"/task/{task_id}/status"
|
|
533
|
+
return "/task/unknown/status"
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
class TaskStreamProxyHandler(StreamProxyHandler):
|
|
537
|
+
"""Proxy handler for /task/{id}/stream endpoint."""
|
|
538
|
+
|
|
539
|
+
def get_proxy_path(self) -> str:
|
|
540
|
+
request_path = self.request.path
|
|
541
|
+
parts = request_path.split("/")
|
|
542
|
+
task_idx = parts.index("task") if "task" in parts else -1
|
|
543
|
+
if task_idx >= 0 and task_idx + 1 < len(parts):
|
|
544
|
+
task_id = parts[task_idx + 1]
|
|
545
|
+
return f"/task/{task_id}/stream"
|
|
546
|
+
return "/task/unknown/stream"
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
class TaskCancelProxyHandler(BaseProxyHandler):
|
|
550
|
+
"""Proxy handler for /task/{id}/cancel endpoint."""
|
|
551
|
+
|
|
552
|
+
def get_proxy_path(self) -> str:
|
|
553
|
+
request_path = self.request.path
|
|
554
|
+
parts = request_path.split("/")
|
|
555
|
+
task_idx = parts.index("task") if "task" in parts else -1
|
|
556
|
+
if task_idx >= 0 and task_idx + 1 < len(parts):
|
|
557
|
+
task_id = parts[task_idx + 1]
|
|
558
|
+
return f"/task/{task_id}/cancel"
|
|
559
|
+
return "/task/unknown/cancel"
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
class RAGReindexHandler(APIHandler):
|
|
563
|
+
"""Handler for /rag/reindex endpoint using ServiceFactory."""
|
|
564
|
+
|
|
565
|
+
async def post(self):
|
|
566
|
+
"""Trigger reindex operation."""
|
|
567
|
+
try:
|
|
568
|
+
factory = _get_service_factory()
|
|
569
|
+
rag_service = factory.get_rag_service()
|
|
570
|
+
|
|
571
|
+
body = json.loads(self.request.body.decode("utf-8")) if self.request.body else {}
|
|
572
|
+
force = body.get("force", False)
|
|
573
|
+
|
|
574
|
+
response = await rag_service.trigger_reindex(force=force)
|
|
575
|
+
|
|
576
|
+
self.set_header("Content-Type", "application/json")
|
|
577
|
+
self.write(response)
|
|
578
|
+
|
|
579
|
+
except Exception as e:
|
|
580
|
+
logger.error(f"RAG reindex failed: {e}", exc_info=True)
|
|
581
|
+
self.set_status(500)
|
|
582
|
+
self.write({"error": str(e)})
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
# ============ Handler Setup ============
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def setup_handlers(web_app):
|
|
589
|
+
"""Register all handlers based on execution mode."""
|
|
590
|
+
host_pattern = ".*$"
|
|
591
|
+
base_url = web_app.settings["base_url"]
|
|
592
|
+
|
|
593
|
+
handlers = [
|
|
594
|
+
# Health check
|
|
595
|
+
(url_path_join(base_url, "hdsp-agent", "health"), HealthHandler),
|
|
596
|
+
# Config endpoint (still proxied)
|
|
597
|
+
(url_path_join(base_url, "hdsp-agent", "config"), ConfigProxyHandler),
|
|
598
|
+
|
|
599
|
+
# ===== ServiceFactory-based handlers =====
|
|
600
|
+
# Agent endpoints
|
|
601
|
+
(url_path_join(base_url, "hdsp-agent", "auto-agent", "plan"), AgentPlanHandler),
|
|
602
|
+
(url_path_join(base_url, "hdsp-agent", "auto-agent", "refine"), AgentRefineHandler),
|
|
603
|
+
(url_path_join(base_url, "hdsp-agent", "auto-agent", "replan"), AgentReplanHandler),
|
|
604
|
+
(url_path_join(base_url, "hdsp-agent", "auto-agent", "validate"), AgentValidateHandler),
|
|
605
|
+
|
|
606
|
+
# Chat endpoints
|
|
607
|
+
(url_path_join(base_url, "hdsp-agent", "chat", "message"), ChatMessageHandler),
|
|
608
|
+
(url_path_join(base_url, "hdsp-agent", "chat", "stream"), ChatStreamHandler),
|
|
609
|
+
|
|
610
|
+
# RAG endpoints
|
|
611
|
+
(url_path_join(base_url, "hdsp-agent", "rag", "search"), RAGSearchHandler),
|
|
612
|
+
(url_path_join(base_url, "hdsp-agent", "rag", "status"), RAGStatusHandler),
|
|
613
|
+
(url_path_join(base_url, "hdsp-agent", "rag", "reindex"), RAGReindexHandler),
|
|
614
|
+
|
|
615
|
+
# ===== Proxy-only handlers (not yet migrated to ServiceFactory) =====
|
|
616
|
+
(url_path_join(base_url, "hdsp-agent", "auto-agent", "reflect"), AgentReflectProxyHandler),
|
|
617
|
+
(url_path_join(base_url, "hdsp-agent", "auto-agent", "verify-state"), AgentVerifyStateProxyHandler),
|
|
618
|
+
(url_path_join(base_url, "hdsp-agent", "auto-agent", "plan", "stream"), AgentPlanStreamProxyHandler),
|
|
619
|
+
|
|
620
|
+
# Cell/File action endpoints
|
|
621
|
+
(url_path_join(base_url, "hdsp-agent", "cell", "action"), CellActionProxyHandler),
|
|
622
|
+
(url_path_join(base_url, "hdsp-agent", "file", "action"), FileActionProxyHandler),
|
|
623
|
+
(url_path_join(base_url, "hdsp-agent", "file", "resolve"), FileResolveProxyHandler),
|
|
624
|
+
(url_path_join(base_url, "hdsp-agent", "file", "select"), FileSelectProxyHandler),
|
|
625
|
+
|
|
626
|
+
# Task endpoints
|
|
627
|
+
(url_path_join(base_url, "hdsp-agent", "task", r"([^/]+)", "status"), TaskStatusProxyHandler),
|
|
628
|
+
(url_path_join(base_url, "hdsp-agent", "task", r"([^/]+)", "stream"), TaskStreamProxyHandler),
|
|
629
|
+
(url_path_join(base_url, "hdsp-agent", "task", r"([^/]+)", "cancel"), TaskCancelProxyHandler),
|
|
630
|
+
]
|
|
631
|
+
|
|
632
|
+
web_app.add_handlers(host_pattern, handlers)
|