deepcode-hku 1.0.1__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 (44) hide show
  1. cli/__init__.py +18 -0
  2. cli/cli_app.py +296 -0
  3. cli/cli_interface.py +744 -0
  4. cli/cli_launcher.py +155 -0
  5. cli/main_cli.py +243 -0
  6. cli/workflows/__init__.py +11 -0
  7. cli/workflows/cli_workflow_adapter.py +336 -0
  8. deepcode.py +219 -0
  9. deepcode_hku-1.0.1.dist-info/METADATA +695 -0
  10. deepcode_hku-1.0.1.dist-info/RECORD +44 -0
  11. deepcode_hku-1.0.1.dist-info/WHEEL +5 -0
  12. deepcode_hku-1.0.1.dist-info/entry_points.txt +2 -0
  13. deepcode_hku-1.0.1.dist-info/licenses/LICENSE +21 -0
  14. deepcode_hku-1.0.1.dist-info/top_level.txt +6 -0
  15. tools/__init__.py +0 -0
  16. tools/code_implementation_server.py +1045 -0
  17. tools/code_indexer.py +1657 -0
  18. tools/code_reference_indexer.py +486 -0
  19. tools/command_executor.py +324 -0
  20. tools/git_command.py +356 -0
  21. tools/pdf_converter.py +640 -0
  22. tools/pdf_downloader.py +1370 -0
  23. tools/pdf_utils.py +52 -0
  24. ui/__init__.py +43 -0
  25. ui/app.py +13 -0
  26. ui/components.py +1450 -0
  27. ui/handlers.py +773 -0
  28. ui/layout.py +106 -0
  29. ui/streamlit_app.py +38 -0
  30. ui/styles.py +2116 -0
  31. utils/__init__.py +17 -0
  32. utils/cli_interface.py +459 -0
  33. utils/dialogue_logger.py +671 -0
  34. utils/file_processor.py +426 -0
  35. utils/simple_llm_logger.py +198 -0
  36. workflows/__init__.py +31 -0
  37. workflows/agent_orchestration_engine.py +1371 -0
  38. workflows/agents/__init__.py +13 -0
  39. workflows/agents/code_implementation_agent.py +1093 -0
  40. workflows/agents/memory_agent_concise.py +923 -0
  41. workflows/agents/memory_agent_concise_index.py +935 -0
  42. workflows/code_implementation_workflow.py +924 -0
  43. workflows/code_implementation_workflow_index.py +931 -0
  44. workflows/codebase_index_workflow.py +726 -0
ui/handlers.py ADDED
@@ -0,0 +1,773 @@
1
+ """
2
+ Streamlit Event Handlers Module
3
+
4
+ Contains all event handling and business logic
5
+ """
6
+
7
+ import asyncio
8
+ import time
9
+ import os
10
+ import traceback
11
+ import atexit
12
+ import signal
13
+ from datetime import datetime
14
+ from typing import Dict, Any
15
+
16
+ import streamlit as st
17
+ import nest_asyncio
18
+ import concurrent.futures
19
+
20
+ # Import necessary modules
21
+ from mcp_agent.app import MCPApp
22
+ from workflows.agent_orchestration_engine import (
23
+ execute_multi_agent_research_pipeline,
24
+ execute_chat_based_planning_pipeline,
25
+ )
26
+
27
+
28
+ def _emergency_cleanup():
29
+ """
30
+ Emergency resource cleanup function
31
+ Called when program exits abnormally
32
+ """
33
+ try:
34
+ cleanup_resources()
35
+ except Exception:
36
+ pass # Silent handling to avoid new exceptions during exit
37
+
38
+
39
+ def _signal_handler(signum, frame):
40
+ """
41
+ Signal handler for program termination signals
42
+ """
43
+ try:
44
+ cleanup_resources()
45
+ except Exception:
46
+ pass
47
+ finally:
48
+ # Restore default signal handling and resend signal
49
+ signal.signal(signum, signal.SIG_DFL)
50
+ os.kill(os.getpid(), signum)
51
+
52
+
53
+ # Register exit cleanup function
54
+ atexit.register(_emergency_cleanup)
55
+
56
+
57
+ def _safe_register_signal_handlers():
58
+ """Safely register signal handlers"""
59
+ try:
60
+ # Check if running in main thread
61
+ import threading
62
+
63
+ if threading.current_thread() is not threading.main_thread():
64
+ return # Signal handlers can only be registered in main thread
65
+
66
+ # Try to register signal handlers
67
+ signal.signal(signal.SIGTERM, _signal_handler)
68
+ signal.signal(signal.SIGINT, _signal_handler)
69
+ if hasattr(signal, "SIGBREAK"): # Windows
70
+ signal.signal(signal.SIGBREAK, _signal_handler)
71
+ except (AttributeError, OSError, ValueError):
72
+ # Some signals are not available on certain platforms or disabled in some environments
73
+ # This is common in web frameworks like Streamlit
74
+ pass
75
+
76
+
77
+ # Delayed signal handler registration to avoid import-time errors
78
+ try:
79
+ _safe_register_signal_handlers()
80
+ except Exception:
81
+ # If registration fails, silently ignore and don't affect app startup
82
+ pass
83
+
84
+
85
+ async def process_input_async(
86
+ input_source: str,
87
+ input_type: str,
88
+ enable_indexing: bool = True,
89
+ progress_callback=None,
90
+ ) -> Dict[str, Any]:
91
+ """
92
+ Process input asynchronously
93
+
94
+ Args:
95
+ input_source: Input source
96
+ input_type: Input type
97
+ enable_indexing: Whether to enable indexing functionality
98
+ progress_callback: Progress callback function
99
+
100
+ Returns:
101
+ Processing result
102
+ """
103
+ try:
104
+ # Create and use MCP app in the same async context
105
+ app = MCPApp(name="paper_to_code")
106
+
107
+ async with app.run() as agent_app:
108
+ logger = agent_app.logger
109
+ context = agent_app.context
110
+ context.config.mcp.servers["filesystem"].args.extend([os.getcwd()])
111
+
112
+ # Initialize progress
113
+ if progress_callback:
114
+ if input_type == "chat":
115
+ progress_callback(
116
+ 5, "🚀 Initializing chat-based planning pipeline..."
117
+ )
118
+ else:
119
+ progress_callback(5, "🚀 Initializing AI research engine...")
120
+
121
+ # Choose pipeline based on input type
122
+ if input_type == "chat":
123
+ # Use chat-based planning pipeline for user requirements
124
+ repo_result = await execute_chat_based_planning_pipeline(
125
+ input_source, # User's coding requirements
126
+ logger,
127
+ progress_callback,
128
+ enable_indexing=enable_indexing, # Pass indexing control parameter
129
+ )
130
+ else:
131
+ # Use traditional multi-agent research pipeline for files/URLs
132
+ repo_result = await execute_multi_agent_research_pipeline(
133
+ input_source,
134
+ logger,
135
+ progress_callback,
136
+ enable_indexing=enable_indexing, # Pass indexing control parameter
137
+ )
138
+
139
+ return {
140
+ "analysis_result": "Integrated into complete workflow",
141
+ "download_result": "Integrated into complete workflow",
142
+ "repo_result": repo_result,
143
+ "status": "success",
144
+ }
145
+
146
+ except Exception as e:
147
+ error_msg = str(e)
148
+ traceback_msg = traceback.format_exc()
149
+
150
+ return {"error": error_msg, "traceback": traceback_msg, "status": "error"}
151
+
152
+
153
+ def run_async_task(coro):
154
+ """
155
+ Helper function to run async tasks
156
+
157
+ Args:
158
+ coro: Coroutine object
159
+
160
+ Returns:
161
+ Task result
162
+ """
163
+ # Apply nest_asyncio to support nested event loops
164
+ nest_asyncio.apply()
165
+
166
+ # Save current Streamlit context
167
+ try:
168
+ from streamlit.runtime.scriptrunner import get_script_run_ctx
169
+ from streamlit.runtime.scriptrunner.script_run_context import (
170
+ SCRIPT_RUN_CONTEXT_ATTR_NAME,
171
+ )
172
+
173
+ current_ctx = get_script_run_ctx()
174
+ context_available = True
175
+ except ImportError:
176
+ # If Streamlit context modules can't be imported, use fallback method
177
+ current_ctx = None
178
+ context_available = False
179
+
180
+ def run_in_new_loop():
181
+ """Run coroutine in new event loop"""
182
+ # Set Streamlit context in new thread (if available)
183
+ if context_available and current_ctx:
184
+ try:
185
+ import threading
186
+
187
+ setattr(
188
+ threading.current_thread(),
189
+ SCRIPT_RUN_CONTEXT_ATTR_NAME,
190
+ current_ctx,
191
+ )
192
+ except Exception:
193
+ pass # Ignore context setting errors
194
+
195
+ loop = None
196
+ try:
197
+ loop = asyncio.new_event_loop()
198
+ asyncio.set_event_loop(loop)
199
+ result = loop.run_until_complete(coro)
200
+ return result
201
+ except Exception as e:
202
+ raise e
203
+ finally:
204
+ # Clean up resources
205
+ if loop:
206
+ try:
207
+ loop.close()
208
+ except Exception:
209
+ pass
210
+ asyncio.set_event_loop(None)
211
+
212
+ # Clean up thread context (if available)
213
+ if context_available:
214
+ try:
215
+ import threading
216
+
217
+ if hasattr(
218
+ threading.current_thread(), SCRIPT_RUN_CONTEXT_ATTR_NAME
219
+ ):
220
+ delattr(
221
+ threading.current_thread(), SCRIPT_RUN_CONTEXT_ATTR_NAME
222
+ )
223
+ except Exception:
224
+ pass # Ignore cleanup errors
225
+
226
+ # Force garbage collection
227
+ import gc
228
+
229
+ gc.collect()
230
+
231
+ # Use thread pool to run async task, avoiding event loop conflicts
232
+ executor = None
233
+ try:
234
+ executor = concurrent.futures.ThreadPoolExecutor(
235
+ max_workers=1, thread_name_prefix="deepcode_ctx_async"
236
+ )
237
+ future = executor.submit(run_in_new_loop)
238
+ result = future.result(timeout=300) # 5 minute timeout
239
+ return result
240
+ except concurrent.futures.TimeoutError:
241
+ st.error("Processing timeout after 5 minutes. Please try again.")
242
+ raise TimeoutError("Processing timeout")
243
+ except Exception as e:
244
+ # If thread pool execution fails, try direct execution
245
+ st.warning(f"Threaded async execution failed: {e}, trying direct execution...")
246
+ try:
247
+ # Fallback method: run directly in current thread
248
+ loop = None
249
+ try:
250
+ loop = asyncio.new_event_loop()
251
+ asyncio.set_event_loop(loop)
252
+ result = loop.run_until_complete(coro)
253
+ return result
254
+ finally:
255
+ if loop:
256
+ try:
257
+ loop.close()
258
+ except Exception:
259
+ pass
260
+ asyncio.set_event_loop(None)
261
+ import gc
262
+
263
+ gc.collect()
264
+ except Exception as backup_error:
265
+ st.error(f"All execution methods failed: {backup_error}")
266
+ raise backup_error
267
+ finally:
268
+ # Ensure thread pool is properly closed
269
+ if executor:
270
+ try:
271
+ executor.shutdown(wait=True, cancel_futures=True)
272
+ except Exception:
273
+ pass
274
+ # Force garbage collection
275
+ import gc
276
+
277
+ gc.collect()
278
+
279
+
280
+ def run_async_task_simple(coro):
281
+ """
282
+ Simple async task runner, avoiding threading issues
283
+
284
+ Args:
285
+ coro: Coroutine object
286
+
287
+ Returns:
288
+ Task result
289
+ """
290
+ # Apply nest_asyncio to support nested event loops
291
+ nest_asyncio.apply()
292
+
293
+ try:
294
+ # Try to run in current event loop
295
+ loop = asyncio.get_event_loop()
296
+ if loop.is_running():
297
+ # If current loop is running, use improved thread pool method
298
+ import concurrent.futures
299
+ import gc
300
+
301
+ def run_in_thread():
302
+ # Create new event loop and set as current thread's loop
303
+ new_loop = asyncio.new_event_loop()
304
+ asyncio.set_event_loop(new_loop)
305
+ try:
306
+ result = new_loop.run_until_complete(coro)
307
+ return result
308
+ except Exception as e:
309
+ # Ensure exception information is properly passed
310
+ raise e
311
+ finally:
312
+ # Ensure loop is properly closed
313
+ try:
314
+ new_loop.close()
315
+ except Exception:
316
+ pass
317
+ # Clear current thread's event loop reference
318
+ asyncio.set_event_loop(None)
319
+ # Force garbage collection
320
+ gc.collect()
321
+
322
+ # Use context manager to ensure thread pool is properly closed
323
+ executor = None
324
+ try:
325
+ executor = concurrent.futures.ThreadPoolExecutor(
326
+ max_workers=1, thread_name_prefix="deepcode_async"
327
+ )
328
+ future = executor.submit(run_in_thread)
329
+ result = future.result(timeout=300) # 5 minute timeout
330
+ return result
331
+ except concurrent.futures.TimeoutError:
332
+ st.error(
333
+ "Processing timeout after 5 minutes. Please try again with a smaller file."
334
+ )
335
+ raise TimeoutError("Processing timeout")
336
+ except Exception as e:
337
+ st.error(f"Async processing error: {e}")
338
+ raise e
339
+ finally:
340
+ # Ensure thread pool is properly closed
341
+ if executor:
342
+ try:
343
+ executor.shutdown(wait=True, cancel_futures=True)
344
+ except Exception:
345
+ pass
346
+ # Force garbage collection
347
+ gc.collect()
348
+ else:
349
+ # Run directly in current loop
350
+ return loop.run_until_complete(coro)
351
+ except Exception:
352
+ # Final fallback method: create new event loop
353
+ loop = None
354
+ try:
355
+ loop = asyncio.new_event_loop()
356
+ asyncio.set_event_loop(loop)
357
+ result = loop.run_until_complete(coro)
358
+ return result
359
+ except Exception as backup_error:
360
+ st.error(f"All async methods failed: {backup_error}")
361
+ raise backup_error
362
+ finally:
363
+ if loop:
364
+ try:
365
+ loop.close()
366
+ except Exception:
367
+ pass
368
+ asyncio.set_event_loop(None)
369
+ # Force garbage collection
370
+ import gc
371
+
372
+ gc.collect()
373
+
374
+
375
+ def handle_processing_workflow(
376
+ input_source: str, input_type: str, enable_indexing: bool = True
377
+ ) -> Dict[str, Any]:
378
+ """
379
+ Main processing function for workflow
380
+
381
+ Args:
382
+ input_source: Input source
383
+ input_type: Input type
384
+ enable_indexing: Whether to enable indexing functionality
385
+
386
+ Returns:
387
+ Processing result
388
+ """
389
+ from .components import (
390
+ enhanced_progress_display_component,
391
+ update_step_indicator,
392
+ display_status,
393
+ )
394
+
395
+ # Display enhanced progress components
396
+ chat_mode = input_type == "chat"
397
+ progress_bar, status_text, step_indicators, workflow_steps = (
398
+ enhanced_progress_display_component(enable_indexing, chat_mode)
399
+ )
400
+
401
+ # Step mapping: map progress percentages to step indices - adjust based on mode and indexing toggle
402
+ if chat_mode:
403
+ # Chat mode step mapping: Initialize -> Planning -> Setup -> Save Plan -> Implement
404
+ step_mapping = {
405
+ 5: 0, # Initialize
406
+ 30: 1, # Planning (analyzing requirements)
407
+ 50: 2, # Setup (creating workspace)
408
+ 70: 3, # Save Plan (saving implementation plan)
409
+ 85: 4, # Implement (generating code)
410
+ 100: 4, # Complete
411
+ }
412
+ elif not enable_indexing:
413
+ # Skip indexing-related steps progress mapping - fast mode order: Initialize -> Analyze -> Download -> Plan -> Implement
414
+ step_mapping = {
415
+ 5: 0, # Initialize
416
+ 10: 1, # Analyze
417
+ 25: 2, # Download
418
+ 40: 3, # Plan (now prioritized over References, 40%)
419
+ 85: 4, # Implement (skip References, Repos and Index)
420
+ 100: 4, # Complete
421
+ }
422
+ else:
423
+ # Full workflow step mapping - new order: Initialize -> Analyze -> Download -> Plan -> References -> Repos -> Index -> Implement
424
+ step_mapping = {
425
+ 5: 0, # Initialize
426
+ 10: 1, # Analyze
427
+ 25: 2, # Download
428
+ 40: 3, # Plan (now 4th position, 40%)
429
+ 50: 4, # References (now 5th position, conditional, 50%)
430
+ 60: 5, # Repos (GitHub download)
431
+ 70: 6, # Index (code indexing)
432
+ 85: 7, # Implement (code implementation)
433
+ 100: 7, # Complete
434
+ }
435
+
436
+ current_step = 0
437
+
438
+ # Define enhanced progress callback function
439
+ def update_progress(progress: int, message: str):
440
+ nonlocal current_step
441
+
442
+ # Update progress bar
443
+ progress_bar.progress(progress)
444
+ status_text.markdown(f"**{message}**")
445
+
446
+ # Determine current step
447
+ new_step = step_mapping.get(progress, current_step)
448
+ if new_step != current_step:
449
+ current_step = new_step
450
+ update_step_indicator(
451
+ step_indicators, workflow_steps, current_step, "active"
452
+ )
453
+
454
+ time.sleep(0.3) # Brief pause for users to see progress changes
455
+
456
+ # Step 1: Initialization
457
+ if chat_mode:
458
+ update_progress(5, "🚀 Initializing chat-based planning engine...")
459
+ elif enable_indexing:
460
+ update_progress(5, "🚀 Initializing AI research engine and loading models...")
461
+ else:
462
+ update_progress(
463
+ 5, "🚀 Initializing AI research engine (Fast mode - indexing disabled)..."
464
+ )
465
+ update_step_indicator(step_indicators, workflow_steps, 0, "active")
466
+
467
+ # Start async processing with progress callback
468
+ with st.spinner("🔄 Processing workflow stages..."):
469
+ try:
470
+ # First try using simple async processing method
471
+ result = run_async_task_simple(
472
+ process_input_async(
473
+ input_source, input_type, enable_indexing, update_progress
474
+ )
475
+ )
476
+ except Exception as e:
477
+ st.warning(f"Primary async method failed: {e}")
478
+ # Fallback method: use original thread pool method
479
+ try:
480
+ result = run_async_task(
481
+ process_input_async(
482
+ input_source, input_type, enable_indexing, update_progress
483
+ )
484
+ )
485
+ except Exception as backup_error:
486
+ st.error(f"Both async methods failed. Error: {backup_error}")
487
+ return {
488
+ "status": "error",
489
+ "error": str(backup_error),
490
+ "traceback": traceback.format_exc(),
491
+ }
492
+
493
+ # Update final status based on results
494
+ if result["status"] == "success":
495
+ # Complete all steps
496
+ update_progress(100, "✅ All processing stages completed successfully!")
497
+ update_step_indicator(
498
+ step_indicators, workflow_steps, len(workflow_steps), "completed"
499
+ )
500
+
501
+ # Display success information
502
+ st.balloons() # Add celebration animation
503
+ if chat_mode:
504
+ display_status(
505
+ "🎉 Chat workflow completed! Your requirements have been analyzed and code has been generated.",
506
+ "success",
507
+ )
508
+ elif enable_indexing:
509
+ display_status(
510
+ "🎉 Workflow completed! Your research paper has been successfully processed and code has been generated.",
511
+ "success",
512
+ )
513
+ else:
514
+ display_status(
515
+ "🎉 Fast workflow completed! Your research paper has been processed (indexing skipped for faster processing).",
516
+ "success",
517
+ )
518
+
519
+ else:
520
+ # Processing failed
521
+ update_progress(0, "❌ Processing failed - see error details below")
522
+ update_step_indicator(step_indicators, workflow_steps, current_step, "error")
523
+ display_status(
524
+ f"❌ Processing encountered an error: {result.get('error', 'Unknown error')}",
525
+ "error",
526
+ )
527
+
528
+ # Wait a moment for users to see completion status
529
+ time.sleep(2.5)
530
+
531
+ return result
532
+
533
+
534
+ def update_session_state_with_result(result: Dict[str, Any], input_type: str):
535
+ """
536
+ Update session state with result
537
+
538
+ Args:
539
+ result: Processing result
540
+ input_type: Input type
541
+ """
542
+ if result["status"] == "success":
543
+ # Save result to session state
544
+ st.session_state.last_result = result
545
+ st.session_state.show_results = True
546
+
547
+ # Save to history
548
+ st.session_state.results.append(
549
+ {
550
+ "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
551
+ "input_type": input_type,
552
+ "status": "success",
553
+ "result": result,
554
+ }
555
+ )
556
+ else:
557
+ # Save error information to session state for display
558
+ st.session_state.last_error = result.get("error", "Unknown error")
559
+
560
+ # Save error to history
561
+ st.session_state.results.append(
562
+ {
563
+ "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
564
+ "input_type": input_type,
565
+ "status": "error",
566
+ "error": result.get("error", "Unknown error"),
567
+ }
568
+ )
569
+
570
+ # Limit history to maximum 50 records
571
+ if len(st.session_state.results) > 50:
572
+ st.session_state.results = st.session_state.results[-50:]
573
+
574
+
575
+ def cleanup_temp_file(input_source: str, input_type: str):
576
+ """
577
+ Cleanup temporary file
578
+
579
+ Args:
580
+ input_source: Input source
581
+ input_type: Input type
582
+ """
583
+ if input_type == "file" and input_source and os.path.exists(input_source):
584
+ try:
585
+ os.unlink(input_source)
586
+ except Exception:
587
+ pass
588
+
589
+
590
+ def handle_start_processing_button(input_source: str, input_type: str):
591
+ """
592
+ Handle start processing button click
593
+
594
+ Args:
595
+ input_source: Input source
596
+ input_type: Input type
597
+ """
598
+ from .components import display_status
599
+
600
+ st.session_state.processing = True
601
+
602
+ # Get indexing toggle status
603
+ enable_indexing = st.session_state.get("enable_indexing", True)
604
+
605
+ try:
606
+ # Process workflow
607
+ result = handle_processing_workflow(input_source, input_type, enable_indexing)
608
+
609
+ # Display result status
610
+ if result["status"] == "success":
611
+ display_status("All operations completed successfully! 🎉", "success")
612
+ else:
613
+ display_status("Error during processing", "error")
614
+
615
+ # Update session state
616
+ update_session_state_with_result(result, input_type)
617
+
618
+ except Exception as e:
619
+ # Handle exceptional cases
620
+ st.error(f"Unexpected error during processing: {e}")
621
+ result = {"status": "error", "error": str(e)}
622
+ update_session_state_with_result(result, input_type)
623
+
624
+ finally:
625
+ # Reset state and clean up resources after processing
626
+ st.session_state.processing = False
627
+
628
+ # Clean up temporary files
629
+ cleanup_temp_file(input_source, input_type)
630
+
631
+ # Clean up system resources
632
+ cleanup_resources()
633
+
634
+ # Rerun to display results or errors
635
+ st.rerun()
636
+
637
+
638
+ def handle_error_display():
639
+ """Handle error display"""
640
+ if hasattr(st.session_state, "last_error") and st.session_state.last_error:
641
+ st.error(f"❌ Error: {st.session_state.last_error}")
642
+ if st.button("🔄 Try Again", type="secondary", use_container_width=True):
643
+ st.session_state.last_error = None
644
+ st.session_state.task_counter += 1
645
+ st.rerun()
646
+
647
+
648
+ def initialize_session_state():
649
+ """Initialize session state"""
650
+ if "processing" not in st.session_state:
651
+ st.session_state.processing = False
652
+ if "results" not in st.session_state:
653
+ st.session_state.results = []
654
+ if "current_step" not in st.session_state:
655
+ st.session_state.current_step = 0
656
+ if "task_counter" not in st.session_state:
657
+ st.session_state.task_counter = 0
658
+ if "show_results" not in st.session_state:
659
+ st.session_state.show_results = False
660
+ if "last_result" not in st.session_state:
661
+ st.session_state.last_result = None
662
+ if "last_error" not in st.session_state:
663
+ st.session_state.last_error = None
664
+ if "enable_indexing" not in st.session_state:
665
+ st.session_state.enable_indexing = (
666
+ False # Default enable indexing functionality
667
+ )
668
+
669
+
670
+ def cleanup_resources():
671
+ """
672
+ Clean up system resources to prevent memory leaks
673
+ """
674
+ try:
675
+ import gc
676
+ import threading
677
+ import multiprocessing
678
+ import asyncio
679
+ import sys
680
+
681
+ # 1. Clean up asyncio-related resources
682
+ try:
683
+ # Get current event loop (if exists)
684
+ try:
685
+ loop = asyncio.get_running_loop()
686
+ # Cancel all pending tasks
687
+ if loop and not loop.is_closed():
688
+ pending_tasks = [
689
+ task for task in asyncio.all_tasks(loop) if not task.done()
690
+ ]
691
+ if pending_tasks:
692
+ for task in pending_tasks:
693
+ if not task.cancelled():
694
+ task.cancel()
695
+ # Wait for task cancellation to complete
696
+ try:
697
+ if pending_tasks:
698
+ # Use timeout to avoid blocking too long
699
+ import time
700
+
701
+ time.sleep(0.1)
702
+ except Exception:
703
+ pass
704
+ except RuntimeError:
705
+ # No running event loop, continue with other cleanup
706
+ pass
707
+ except Exception:
708
+ pass
709
+
710
+ # 2. Force garbage collection
711
+ gc.collect()
712
+
713
+ # 3. Clean up active threads (except main thread)
714
+ active_threads = threading.active_count()
715
+ if active_threads > 1:
716
+ # Wait some time for threads to naturally finish
717
+ import time
718
+
719
+ time.sleep(0.5)
720
+
721
+ # 4. Clean up multiprocessing resources
722
+ try:
723
+ # Clean up possible multiprocessing resources
724
+ if hasattr(multiprocessing, "active_children"):
725
+ for child in multiprocessing.active_children():
726
+ if child.is_alive():
727
+ child.terminate()
728
+ child.join(timeout=1.0)
729
+ # If join times out, force kill
730
+ if child.is_alive():
731
+ try:
732
+ child.kill()
733
+ child.join(timeout=0.5)
734
+ except Exception:
735
+ pass
736
+
737
+ # Clean up multiprocessing-related resource tracker
738
+ try:
739
+ import multiprocessing.resource_tracker
740
+
741
+ if hasattr(multiprocessing.resource_tracker, "_resource_tracker"):
742
+ tracker = multiprocessing.resource_tracker._resource_tracker
743
+ if tracker and hasattr(tracker, "_stop"):
744
+ tracker._stop()
745
+ except Exception:
746
+ pass
747
+
748
+ except Exception:
749
+ pass
750
+
751
+ # 5. Force clean up Python internal caches
752
+ try:
753
+ # Clean up some temporary objects in module cache
754
+ import sys
755
+
756
+ # Don't delete key modules, only clean up possible temporary resources
757
+ if hasattr(sys, "_clear_type_cache"):
758
+ sys._clear_type_cache()
759
+ except Exception:
760
+ pass
761
+
762
+ # 6. Final garbage collection
763
+ gc.collect()
764
+
765
+ except Exception as e:
766
+ # Silently handle cleanup errors to avoid affecting main flow
767
+ # But can log errors in debug mode
768
+ try:
769
+ import logging
770
+
771
+ logging.getLogger(__name__).debug(f"Resource cleanup warning: {e}")
772
+ except Exception:
773
+ pass