1- // Python executor - Handles Python code execution
1+ // py-executor/src/index.ts
2+
3+ declare const self : DedicatedWorkerGlobalScope ;
4+
5+ // Import Pyodide dynamically to avoid module resolution issues
6+ const PYODIDE_VERSION = "0.28.0" ;
7+
28export class PythonExecutor {
3- constructor ( ) {
4- // Initialize Python executor
5- }
6-
7- initialize ( ) : void {
8- // TODO: Setup postMessage communication with core
9- // TODO: Setup sandboxed execution environment (likely Pyodide)
10- }
11-
12- private executeCode ( code : string ) : void {
13- // TODO: Implement Python code execution logic
14- }
9+ private pyodide : any | null = null ;
10+ private inputResolve : ( ( value : string ) => void ) | null = null ;
11+
12+ constructor ( ) {
13+ this . initialize ( ) ;
14+ }
15+
16+ async initialize ( ) : Promise < void > {
17+ try {
18+ console . log ( 'Starting Pyodide initialization...' ) ;
19+
20+ // Dynamically import Pyodide
21+ const pyodideModule = await import ( `https://cdn.jsdelivr.net/pyodide/v${ PYODIDE_VERSION } /full/pyodide.mjs` ) ;
22+
23+ this . pyodide = await pyodideModule . loadPyodide ( {
24+ indexURL : `https://cdn.jsdelivr.net/pyodide/v${ PYODIDE_VERSION } /full/`
25+ } ) ;
26+
27+ console . log ( 'Pyodide loaded successfully' ) ;
28+
29+ // Setup output handlers
30+ this . pyodide . setStdout ( { batched : ( s : string ) => self . postMessage ( { type : 'stdout' , data : s } ) } ) ;
31+ this . pyodide . setStderr ( { batched : ( s : string ) => self . postMessage ( { type : 'stderr' , data : s } ) } ) ;
32+
33+ // Set up async input function for browser communication
34+ this . pyodide . globals . set ( "browser_input" , ( prompt : string ) => {
35+ return new Promise ( ( resolve ) => {
36+ this . inputResolve = resolve ;
37+ self . postMessage ( { type : 'input_request' , prompt : prompt || "Enter value:" } ) ;
38+ } ) ;
39+ } ) ;
40+
41+ console . log ( 'Pyodide setup complete' ) ;
42+ self . postMessage ( { type : 'ready' } ) ;
43+ } catch ( error : any ) {
44+ console . error ( 'Pyodide initialization error:' , error ) ;
45+ self . postMessage ( { type : 'error' , data : "Failed to load Pyodide: " + error . message } ) ;
46+ }
47+
48+ // Setup message handler
49+ self . onmessage = ( event : MessageEvent ) => this . handleMessage ( event ) ;
50+ }
51+
52+ private async handleMessage ( event : MessageEvent ) : Promise < void > {
53+ const { type, value, code } = event . data ;
54+ console . log ( 'Worker received message:' , { type, value, code : code ?. substring ( 0 , 50 ) + '...' } ) ;
55+
56+ if ( type === 'input_response' && this . inputResolve ) {
57+ this . inputResolve ( value || "" ) ;
58+ this . inputResolve = null ;
59+ return ;
60+ }
61+
62+ if ( this . pyodide && code ) {
63+ this . executeCode ( code ) ;
64+ }
65+ }
66+
67+ private transformInputCalls ( code : string ) : string {
68+ // Transform input() calls to await async_input() calls
69+ // This regex handles various input() patterns:
70+ // - input()
71+ // - input("prompt")
72+ // - input('prompt')
73+ // - variable = input(...)
74+ return code . replace ( / \b i n p u t \s * \( / g, 'await async_input(' ) ;
75+ }
76+
77+ private async executeCode ( code : string ) : Promise < void > {
78+ if ( ! this . pyodide ) return ;
79+
80+ try {
81+ console . log ( 'Executing Python code...' ) ;
82+ await this . pyodide . loadPackagesFromImports ( code ) ;
83+
84+ // Setup the async input function in Python
85+ const inputSetup = `
86+ import builtins
87+
88+ # Define async input function that communicates with browser
89+ async def async_input(prompt=""):
90+ """Async input function that communicates with the browser main thread"""
91+ return await browser_input(str(prompt))
92+
93+ # Keep original input for reference (in case needed)
94+ _original_input = builtins.input
95+ ` ;
96+
97+ await this . pyodide . runPythonAsync ( inputSetup ) ;
98+
99+ // Check if code contains input() calls and transform them
100+ const hasInputCalls = / \b i n p u t \s * \( / . test ( code ) ;
101+
102+ if ( hasInputCalls ) {
103+ console . log ( 'Code contains input() calls, transforming to async...' ) ;
104+
105+ // Transform input() calls to await async_input()
106+ const transformedCode = this . transformInputCalls ( code ) ;
107+
108+ // Wrap in async function to handle await calls
109+ const asyncWrapper = `
110+ import asyncio
111+
112+ async def __main__():
113+ ${ transformedCode . split ( '\n' ) . map ( line => ' ' + line ) . join ( '\n' ) }
114+
115+ # Run the async main function
116+ await __main__()
117+ ` ;
118+ console . log ( 'Running transformed async code...' ) ;
119+ await this . pyodide . runPythonAsync ( asyncWrapper ) ;
120+ } else {
121+ // No input calls, run synchronously
122+ console . log ( 'No input() calls detected, running synchronously...' ) ;
123+ await this . pyodide . runPython ( code ) ;
124+ }
125+
126+ console . log ( 'Python execution completed' ) ;
127+ self . postMessage ( { type : 'done' } ) ;
128+ } catch ( error : any ) {
129+ console . error ( 'Python execution error:' , error ) ;
130+ self . postMessage ( { type : 'error' , data : error . message } ) ;
131+ }
132+ }
15133}
16134
17- export default PythonExecutor ;
135+ // Instantiate the executor to start the worker
136+ console . log ( 'Creating PythonExecutor instance...' ) ;
137+ new PythonExecutor ( ) ;
0 commit comments