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.
Files changed (121) hide show
  1. agent_server/__init__.py +8 -0
  2. agent_server/core/__init__.py +92 -0
  3. agent_server/core/api_key_manager.py +427 -0
  4. agent_server/core/code_validator.py +1238 -0
  5. agent_server/core/context_condenser.py +308 -0
  6. agent_server/core/embedding_service.py +254 -0
  7. agent_server/core/error_classifier.py +577 -0
  8. agent_server/core/llm_client.py +95 -0
  9. agent_server/core/llm_service.py +649 -0
  10. agent_server/core/notebook_generator.py +274 -0
  11. agent_server/core/prompt_builder.py +35 -0
  12. agent_server/core/rag_manager.py +742 -0
  13. agent_server/core/reflection_engine.py +489 -0
  14. agent_server/core/retriever.py +248 -0
  15. agent_server/core/state_verifier.py +452 -0
  16. agent_server/core/summary_generator.py +484 -0
  17. agent_server/core/task_manager.py +198 -0
  18. agent_server/knowledge/__init__.py +9 -0
  19. agent_server/knowledge/watchdog_service.py +352 -0
  20. agent_server/main.py +160 -0
  21. agent_server/prompts/__init__.py +60 -0
  22. agent_server/prompts/file_action_prompts.py +113 -0
  23. agent_server/routers/__init__.py +9 -0
  24. agent_server/routers/agent.py +591 -0
  25. agent_server/routers/chat.py +188 -0
  26. agent_server/routers/config.py +100 -0
  27. agent_server/routers/file_resolver.py +293 -0
  28. agent_server/routers/health.py +42 -0
  29. agent_server/routers/rag.py +163 -0
  30. agent_server/schemas/__init__.py +60 -0
  31. hdsp_agent_core/__init__.py +158 -0
  32. hdsp_agent_core/factory.py +252 -0
  33. hdsp_agent_core/interfaces.py +203 -0
  34. hdsp_agent_core/knowledge/__init__.py +31 -0
  35. hdsp_agent_core/knowledge/chunking.py +356 -0
  36. hdsp_agent_core/knowledge/libraries/dask.md +188 -0
  37. hdsp_agent_core/knowledge/libraries/matplotlib.md +164 -0
  38. hdsp_agent_core/knowledge/libraries/polars.md +68 -0
  39. hdsp_agent_core/knowledge/loader.py +337 -0
  40. hdsp_agent_core/llm/__init__.py +13 -0
  41. hdsp_agent_core/llm/service.py +556 -0
  42. hdsp_agent_core/managers/__init__.py +22 -0
  43. hdsp_agent_core/managers/config_manager.py +133 -0
  44. hdsp_agent_core/managers/session_manager.py +251 -0
  45. hdsp_agent_core/models/__init__.py +115 -0
  46. hdsp_agent_core/models/agent.py +316 -0
  47. hdsp_agent_core/models/chat.py +41 -0
  48. hdsp_agent_core/models/common.py +95 -0
  49. hdsp_agent_core/models/rag.py +368 -0
  50. hdsp_agent_core/prompts/__init__.py +63 -0
  51. hdsp_agent_core/prompts/auto_agent_prompts.py +1260 -0
  52. hdsp_agent_core/prompts/cell_action_prompts.py +98 -0
  53. hdsp_agent_core/services/__init__.py +18 -0
  54. hdsp_agent_core/services/agent_service.py +438 -0
  55. hdsp_agent_core/services/chat_service.py +205 -0
  56. hdsp_agent_core/services/rag_service.py +262 -0
  57. hdsp_agent_core/tests/__init__.py +1 -0
  58. hdsp_agent_core/tests/conftest.py +102 -0
  59. hdsp_agent_core/tests/test_factory.py +251 -0
  60. hdsp_agent_core/tests/test_services.py +326 -0
  61. hdsp_jupyter_extension-2.0.0.data/data/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +7 -0
  62. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/build_log.json +738 -0
  63. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/install.json +5 -0
  64. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/package.json +134 -0
  65. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2607ff74c74acfa83158.js +4369 -0
  66. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/frontend_styles_index_js.2607ff74c74acfa83158.js.map +1 -0
  67. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.622c1a5918b3aafb2315.js +12496 -0
  68. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/lib_index_js.622c1a5918b3aafb2315.js.map +1 -0
  69. 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
  70. 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
  71. 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
  72. 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
  73. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.dae97cde171e13b8c834.js +623 -0
  74. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/remoteEntry.dae97cde171e13b8c834.js.map +1 -0
  75. hdsp_jupyter_extension-2.0.0.data/data/share/jupyter/labextensions/hdsp-agent/static/style.js +4 -0
  76. 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
  77. 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
  78. 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
  79. 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
  80. 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
  81. 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
  82. 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
  83. 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
  84. 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
  85. 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
  86. 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
  87. 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
  88. hdsp_jupyter_extension-2.0.0.dist-info/METADATA +152 -0
  89. hdsp_jupyter_extension-2.0.0.dist-info/RECORD +121 -0
  90. hdsp_jupyter_extension-2.0.0.dist-info/WHEEL +4 -0
  91. hdsp_jupyter_extension-2.0.0.dist-info/licenses/LICENSE +21 -0
  92. jupyter_ext/__init__.py +233 -0
  93. jupyter_ext/_version.py +4 -0
  94. jupyter_ext/config.py +111 -0
  95. jupyter_ext/etc/jupyter/jupyter_server_config.d/hdsp_jupyter_extension.json +7 -0
  96. jupyter_ext/handlers.py +632 -0
  97. jupyter_ext/labextension/build_log.json +738 -0
  98. jupyter_ext/labextension/package.json +134 -0
  99. jupyter_ext/labextension/static/frontend_styles_index_js.2607ff74c74acfa83158.js +4369 -0
  100. jupyter_ext/labextension/static/frontend_styles_index_js.2607ff74c74acfa83158.js.map +1 -0
  101. jupyter_ext/labextension/static/lib_index_js.622c1a5918b3aafb2315.js +12496 -0
  102. jupyter_ext/labextension/static/lib_index_js.622c1a5918b3aafb2315.js.map +1 -0
  103. jupyter_ext/labextension/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b80.c095373419d05e6f141a.js +94 -0
  104. jupyter_ext/labextension/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b80.c095373419d05e6f141a.js.map +1 -0
  105. jupyter_ext/labextension/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b81.61e75fb98ecff46cf836.js +94 -0
  106. jupyter_ext/labextension/static/node_modules_emotion_use-insertion-effect-with-fallbacks_dist_emotion-use-insertion-effect-wi-3ba6b81.61e75fb98ecff46cf836.js.map +1 -0
  107. jupyter_ext/labextension/static/remoteEntry.dae97cde171e13b8c834.js +623 -0
  108. jupyter_ext/labextension/static/remoteEntry.dae97cde171e13b8c834.js.map +1 -0
  109. jupyter_ext/labextension/static/style.js +4 -0
  110. jupyter_ext/labextension/static/vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195.e2553aab0c3963b83dd7.js +507 -0
  111. jupyter_ext/labextension/static/vendors-node_modules_babel_runtime_helpers_esm_extends_js-node_modules_emotion_serialize_dist-051195.e2553aab0c3963b83dd7.js.map +1 -0
  112. jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js +2071 -0
  113. jupyter_ext/labextension/static/vendors-node_modules_emotion_cache_dist_emotion-cache_browser_development_esm_js-node_modules-782ee5.d9ed8645ef1d311657d8.js.map +1 -0
  114. jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js +1059 -0
  115. jupyter_ext/labextension/static/vendors-node_modules_emotion_react_dist_emotion-react_browser_development_esm_js.36b49c71871f98d4f549.js.map +1 -0
  116. jupyter_ext/labextension/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js +376 -0
  117. jupyter_ext/labextension/static/vendors-node_modules_emotion_styled_dist_emotion-styled_browser_development_esm_js.661fb5836f4978a7c6e1.js.map +1 -0
  118. jupyter_ext/labextension/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js +60336 -0
  119. jupyter_ext/labextension/static/vendors-node_modules_mui_material_index_js.985697e0162d8d088ca2.js.map +1 -0
  120. jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js +7132 -0
  121. jupyter_ext/labextension/static/vendors-node_modules_mui_material_utils_createSvgIcon_js.2e13df4ea61496e95d45.js.map +1 -0
@@ -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)