@@ -96,10 +96,10 @@ class KernelInfo:
9696 last_health_check : datetime = field (default_factory = datetime .now )
9797 current_operation : Optional [str ] = None
9898 failure_count : int = 0
99-
99+
100100 def is_available (self ) -> bool :
101101 return self .state == KernelState .HEALTHY
102-
102+
103103 def needs_health_check (self ) -> bool :
104104 return datetime .now () - self .last_health_check > timedelta (seconds = KERNEL_HEALTH_CHECK_INTERVAL )
105105
@@ -110,21 +110,21 @@ def __init__(self):
110110 self .busy_kernels : Set [str ] = set ()
111111 self ._initialized = False
112112 self ._health_check_task : Optional [asyncio .Task ] = None
113-
113+
114114 async def initialize (self ):
115115 """Initialize the kernel pool with minimum number of kernels"""
116116 if self ._initialized :
117117 return
118-
118+
119119 async with self .lock :
120120 logger .info ("Initializing kernel pool..." )
121-
121+
122122 # Try to use existing kernel first
123123 existing_kernel = await self ._get_existing_kernel ()
124124 if existing_kernel :
125125 self .kernels [existing_kernel ] = KernelInfo (kernel_id = existing_kernel )
126126 logger .info (f"Added existing kernel to pool: { existing_kernel } " )
127-
127+
128128 # Create additional kernels to reach minimum
129129 while len (self .kernels ) < MIN_KERNELS :
130130 kernel_id = await self ._create_new_kernel ()
@@ -134,17 +134,17 @@ async def initialize(self):
134134 else :
135135 logger .warning ("Failed to create minimum number of kernels" )
136136 break
137-
137+
138138 self ._initialized = True
139139 # Start health check background task
140140 self ._health_check_task = asyncio .create_task (self ._health_check_loop ())
141141 logger .info (f"Kernel pool initialized with { len (self .kernels )} kernels" )
142-
142+
143143 async def get_available_kernel (self ) -> Optional [str ]:
144144 """Get an available kernel from the pool"""
145145 if not self ._initialized :
146146 await self .initialize ()
147-
147+
148148 async with self .lock :
149149 # Find healthy, available kernel
150150 for kernel_id , kernel_info in self .kernels .items ():
@@ -154,7 +154,7 @@ async def get_available_kernel(self) -> Optional[str]:
154154 kernel_info .last_used = datetime .now ()
155155 logger .info (f"Assigned kernel { kernel_id } to operation" )
156156 return kernel_id
157-
157+
158158 # No available kernels, try to create a new one if under limit
159159 if len (self .kernels ) < MAX_KERNELS :
160160 kernel_id = await self ._create_new_kernel ()
@@ -164,23 +164,23 @@ async def get_available_kernel(self) -> Optional[str]:
164164 self .busy_kernels .add (kernel_id )
165165 logger .info (f"Created and assigned new kernel: { kernel_id } " )
166166 return kernel_id
167-
167+
168168 logger .warning ("No available kernels in pool" )
169169 return None
170-
170+
171171 async def release_kernel (self , kernel_id : str , failed : bool = False ):
172172 """Release a kernel back to the pool"""
173173 async with self .lock :
174174 if kernel_id in self .busy_kernels :
175175 self .busy_kernels .remove (kernel_id )
176-
176+
177177 if kernel_id in self .kernels :
178178 kernel_info = self .kernels [kernel_id ]
179179 if failed :
180180 kernel_info .failure_count += 1
181181 kernel_info .state = KernelState .FAILED
182182 logger .warning (f"Kernel { kernel_id } marked as failed (failures: { kernel_info .failure_count } )" )
183-
183+
184184 # Remove failed kernel if it has too many failures
185185 if kernel_info .failure_count >= MAX_RETRY_ATTEMPTS :
186186 await self ._remove_kernel (kernel_id )
@@ -192,7 +192,7 @@ async def release_kernel(self, kernel_id: str, failed: bool = False):
192192 kernel_info .state = KernelState .HEALTHY
193193 kernel_info .current_operation = None
194194 logger .info (f"Released kernel { kernel_id } back to pool" )
195-
195+
196196 async def _get_existing_kernel (self ) -> Optional [str ]:
197197 """Try to get kernel ID from existing file"""
198198 try :
@@ -206,7 +206,7 @@ async def _get_existing_kernel(self) -> Optional[str]:
206206 except Exception as e :
207207 logger .warning (f"Could not read or validate existing kernel from { KERNEL_ID_FILE_PATH } : { e } " )
208208 return None
209-
209+
210210 async def _create_new_kernel (self ) -> Optional [str ]:
211211 """Create a new Jupyter kernel"""
212212 try :
@@ -226,7 +226,7 @@ async def _create_new_kernel(self) -> Optional[str]:
226226 except Exception as e :
227227 logger .error (f"Error creating kernel: { e } " )
228228 return None
229-
229+
230230 async def _remove_kernel (self , kernel_id : str ):
231231 """Remove and shutdown a kernel"""
232232 try :
@@ -238,12 +238,12 @@ async def _remove_kernel(self, kernel_id: str):
238238 logger .info (f"Removed kernel: { kernel_id } " )
239239 except Exception as e :
240240 logger .warning (f"Error removing kernel { kernel_id } : { e } " )
241-
241+
242242 if kernel_id in self .kernels :
243243 del self .kernels [kernel_id ]
244244 if kernel_id in self .busy_kernels :
245245 self .busy_kernels .remove (kernel_id )
246-
246+
247247 async def _check_kernel_health (self , kernel_id : str ) -> bool :
248248 """Check if a kernel is healthy by sending a simple command"""
249249 try :
@@ -256,15 +256,15 @@ async def _check_kernel_health(self, kernel_id: str) -> bool:
256256 # Send simple health check command
257257 msg_id , request_json = create_jupyter_request ("1+1" )
258258 await ws .send (request_json )
259-
259+
260260 # Wait for response with timeout
261261 start_time = time .time ()
262262 while time .time () - start_time < 10 : # 10 second timeout for health check
263263 try :
264264 message_str = await asyncio .wait_for (ws .recv (), timeout = 2.0 )
265265 message_data = json .loads (message_str )
266266 parent_msg_id = message_data .get ("parent_header" , {}).get ("msg_id" )
267-
267+
268268 if parent_msg_id == msg_id :
269269 msg_type = message_data .get ("header" , {}).get ("msg_type" )
270270 if msg_type == "status" and message_data .get ("content" , {}).get ("execution_state" ) == "idle" :
@@ -275,7 +275,7 @@ async def _check_kernel_health(self, kernel_id: str) -> bool:
275275 except Exception as e :
276276 logger .warning (f"Health check failed for kernel { kernel_id } : { e } " )
277277 return False
278-
278+
279279 async def _health_check_loop (self ):
280280 """Background task to monitor kernel health"""
281281 while True :
@@ -291,7 +291,7 @@ async def _health_check_loop(self):
291291 else :
292292 kernel_info .state = KernelState .UNRESPONSIVE
293293 unhealthy_kernels .append (kernel_id )
294-
294+
295295 # Remove unhealthy kernels and create replacements
296296 for kernel_id in unhealthy_kernels :
297297 logger .warning (f"Removing unhealthy kernel: { kernel_id } " )
@@ -346,14 +346,14 @@ def create_jupyter_request(code: str) -> tuple[str, str]:
346346async def execute_with_retry (command : str , ctx : Context , max_attempts : int = MAX_RETRY_ATTEMPTS ) -> str :
347347 """Execute code with retry logic and exponential backoff"""
348348 last_error = None
349-
349+
350350 for attempt in range (max_attempts ):
351351 try :
352352 # Get kernel from pool
353353 kernel_id = await kernel_pool .get_available_kernel ()
354354 if not kernel_id :
355355 raise NoKernelAvailableError ("No available kernels in pool" )
356-
356+
357357 try :
358358 result = await _execute_on_kernel (kernel_id , command , ctx )
359359 # Release kernel back to pool on success
@@ -363,7 +363,7 @@ async def execute_with_retry(command: str, ctx: Context, max_attempts: int = MAX
363363 # Release kernel as failed
364364 await kernel_pool .release_kernel (kernel_id , failed = True )
365365 raise e
366-
366+
367367 except Exception as e :
368368 last_error = e
369369 if attempt < max_attempts - 1 :
@@ -372,7 +372,7 @@ async def execute_with_retry(command: str, ctx: Context, max_attempts: int = MAX
372372 await asyncio .sleep (backoff_time )
373373 else :
374374 logger .error (f"All { max_attempts } execution attempts failed. Last error: { e } " )
375-
375+
376376 return f"Error: Execution failed after { max_attempts } attempts. Last error: { str (last_error )} "
377377
378378async def _execute_on_kernel (kernel_id : str , command : str , ctx : Context ) -> str :
@@ -396,34 +396,34 @@ async def _execute_on_kernel(kernel_id: str, command: str, ctx: Context) -> str:
396396 execution_complete = False
397397 start_time = time .time ()
398398 last_activity = start_time
399-
399+
400400 # Progress reporting for long operations
401- await ctx .report_progress (progress = f"Executing on kernel { kernel_id [:8 ]} ..." )
401+ await ctx .report_progress (progress = 10 , message = f"Executing on kernel { kernel_id [:8 ]} ..." )
402402
403403 while not execution_complete and (time .time () - start_time ) < WEBSOCKET_TIMEOUT :
404404 try :
405405 # Adaptive timeout based on recent activity
406406 current_time = time .time ()
407407 time_since_activity = current_time - last_activity
408-
408+
409409 # Use shorter timeout if no recent activity, longer if active
410410 recv_timeout = 30.0 if time_since_activity > 60 else 5.0
411-
411+
412412 message_str = await asyncio .wait_for (jupyter_ws .recv (), timeout = recv_timeout )
413413 last_activity = current_time
414-
414+
415415 except asyncio .TimeoutError :
416416 # Send periodic progress updates during long operations
417417 elapsed = time .time () - start_time
418- await ctx .report_progress (progress = f"Still executing... ({ elapsed :.0f} s elapsed)" )
418+ await ctx .report_progress (progress = 30 , message = f"Still executing... ({ elapsed :.0f} s elapsed)" )
419419 continue
420420
421421 try :
422422 message_data = json .loads (message_str )
423423 except json .JSONDecodeError :
424424 logger .warning (f"Received invalid JSON from kernel { kernel_id } " )
425425 continue
426-
426+
427427 parent_msg_id = message_data .get ("parent_header" , {}).get ("msg_id" )
428428
429429 if parent_msg_id != sent_msg_id :
@@ -436,20 +436,20 @@ async def _execute_on_kernel(kernel_id: str, command: str, ctx: Context) -> str:
436436 stream_text = content .get ("text" , "" )
437437 final_output_lines .append (stream_text )
438438 # Stream output as progress
439- await ctx .report_progress (progress = stream_text .strip ())
439+ await ctx .report_progress (progress = 50 , message = stream_text .strip ())
440440
441441 elif msg_type in ["execute_result" , "display_data" ]:
442442 result_text = content .get ("data" , {}).get ("text/plain" , "" )
443443 final_output_lines .append (result_text )
444-
444+
445445 elif msg_type == "error" :
446446 error_traceback = "\n " .join (content .get ("traceback" , []))
447447 logger .error (f"Execution error on kernel { kernel_id } for msg_id { sent_msg_id } :\n { error_traceback } " )
448448 raise KernelExecutionError (f"Execution Error:\n { error_traceback } " )
449449
450450 elif msg_type == "status" and content .get ("execution_state" ) == "idle" :
451451 execution_complete = True
452- await ctx .report_progress (progress = "Execution completed" )
452+ await ctx .report_progress (progress = 100 , message = "Execution completed" )
453453
454454 if not execution_complete :
455455 elapsed = time .time () - start_time
@@ -486,13 +486,13 @@ async def execute_python_code(command: str, ctx: Context) -> str:
486486 try :
487487 # Initialize kernel pool if not already done
488488 if not kernel_pool ._initialized :
489- await ctx .report_progress (progress = "Initializing kernel pool..." )
489+ await ctx .report_progress (progress = 10 , message = "Initializing kernel pool..." )
490490 await kernel_pool .initialize ()
491-
491+
492492 # Execute with retry logic
493493 result = await execute_with_retry (command , ctx )
494494 return result
495-
495+
496496 except Exception as e :
497497 logger .error (f"Fatal error in execute_python_code: { e } " , exc_info = True )
498498 return f"Error: Failed to execute code: { str (e )} "
0 commit comments