Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"eslint": "^8.0.0",
"eslint-config-prettier": "^9.0.0",
"prettier": "^3.0.0",
"pyodide": "^0.28.0",
"rimraf": "^6.0.1",
"typescript": "^5.0.0",
"vite": "^7.0.6"
Expand Down
6 changes: 4 additions & 2 deletions packages/py-executor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
"clean": "rm -rf dist",
"test": "echo \"No tests specified\""
},
"dependencies": {},
"dependencies": {
"pyodide": "^0.28.0"
},
"devDependencies": {
"typescript": "^5.0.0"
"typescript": "^5.8.3"
},
"files": [
"dist/**/*"
Expand Down
24 changes: 24 additions & 0 deletions packages/py-executor/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

148 changes: 134 additions & 14 deletions packages/py-executor/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,137 @@
// Python executor - Handles Python code execution
// py-executor/src/index.ts

declare const self: DedicatedWorkerGlobalScope;

// Import Pyodide dynamically to avoid module resolution issues
const PYODIDE_VERSION = "0.28.0";

export class PythonExecutor {
constructor() {
// Initialize Python executor
}

initialize(): void {
// TODO: Setup postMessage communication with core
// TODO: Setup sandboxed execution environment (likely Pyodide)
}

private executeCode(code: string): void {
// TODO: Implement Python code execution logic
}
private pyodide: any | null = null;
private inputResolve: ((value: string) => void) | null = null;

constructor() {
this.initialize();
}

async initialize(): Promise<void> {
try {
console.log('Starting Pyodide initialization...');

// Dynamically import Pyodide
const pyodideModule = await import(`https://cdn.jsdelivr.net/pyodide/v${PYODIDE_VERSION}/full/pyodide.mjs`);

this.pyodide = await pyodideModule.loadPyodide({
indexURL: `https://cdn.jsdelivr.net/pyodide/v${PYODIDE_VERSION}/full/`
});

console.log('Pyodide loaded successfully');

// Setup output handlers
this.pyodide.setStdout({ batched: (s: string) => self.postMessage({ type: 'stdout', data: s }) });
this.pyodide.setStderr({ batched: (s: string) => self.postMessage({ type: 'stderr', data: s }) });

// Set up async input function for browser communication
this.pyodide.globals.set("browser_input", (prompt: string) => {
return new Promise((resolve) => {
this.inputResolve = resolve;
self.postMessage({ type: 'input_request', prompt: prompt || "Enter value:" });
});
});

console.log('Pyodide setup complete');
self.postMessage({ type: 'ready' });
} catch (error: any) {
console.error('Pyodide initialization error:', error);
self.postMessage({ type: 'error', data: "Failed to load Pyodide: " + error.message });
}

// Setup message handler
self.onmessage = (event: MessageEvent) => this.handleMessage(event);
}

private async handleMessage(event: MessageEvent): Promise<void> {
const { type, value, code } = event.data;
console.log('Worker received message:', { type, value, code: code?.substring(0, 50) + '...' });

if (type === 'input_response' && this.inputResolve) {
this.inputResolve(value || "");
this.inputResolve = null;
return;
}

if (this.pyodide && code) {
this.executeCode(code);
}
}

private transformInputCalls(code: string): string {
// Transform input() calls to await async_input() calls
// This regex handles various input() patterns:
// - input()
// - input("prompt")
// - input('prompt')
// - variable = input(...)
return code.replace(/\binput\s*\(/g, 'await async_input(');
}

private async executeCode(code: string): Promise<void> {
if (!this.pyodide) return;

try {
console.log('Executing Python code...');
await this.pyodide.loadPackagesFromImports(code);

// Setup the async input function in Python
const inputSetup = `
import builtins

# Define async input function that communicates with browser
async def async_input(prompt=""):
"""Async input function that communicates with the browser main thread"""
return await browser_input(str(prompt))

# Keep original input for reference (in case needed)
_original_input = builtins.input
`;

await this.pyodide.runPythonAsync(inputSetup);

// Check if code contains input() calls and transform them
const hasInputCalls = /\binput\s*\(/.test(code);

if (hasInputCalls) {
console.log('Code contains input() calls, transforming to async...');

// Transform input() calls to await async_input()
const transformedCode = this.transformInputCalls(code);

// Wrap in async function to handle await calls
const asyncWrapper = `
import asyncio

async def __main__():
${transformedCode.split('\n').map(line => ' ' + line).join('\n')}

# Run the async main function
await __main__()
`;
console.log('Running transformed async code...');
await this.pyodide.runPythonAsync(asyncWrapper);
} else {
// No input calls, run synchronously
console.log('No input() calls detected, running synchronously...');
await this.pyodide.runPython(code);
}

console.log('Python execution completed');
self.postMessage({ type: 'done' });
} catch (error: any) {
console.error('Python execution error:', error);
self.postMessage({ type: 'error', data: error.message });
}
}
}

export default PythonExecutor;
// Instantiate the executor to start the worker
console.log('Creating PythonExecutor instance...');
new PythonExecutor();
6 changes: 3 additions & 3 deletions packages/py-executor/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "node",
"moduleResolution": "bundler", // Change from node to bundler
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
Expand All @@ -12,7 +12,7 @@
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"lib": ["ES2022", "DOM", "DOM.Iterable", "WebWorker"], // Added WebWorker
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"isolatedModules": true,
Expand All @@ -22,4 +22,4 @@
},
"include": ["src/**/*"],
"exclude": ["dist", "node_modules"]
}
}
Loading