-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathcodeBlocks.js
224 lines (197 loc) · 8.22 KB
/
codeBlocks.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
// helper for executing HBS action code blocks and giving them context
const safeEval = require('safe-eval');
const { z } = require('zod');
const { exec } = require('child_process');
const { promisify } = require('util');
const execAsync = promisify(exec);
class codeBlocks {
constructor() {
this.code_blocks = [];
this.currentFolder = process.cwd();
this.x_console = new (require('@concepto/console'))();
this.lastEval = '';
}
async executePython(context = {}, code = '') {
const fs = require('fs');
const path = require('path');
const os = require('os');
const homeDir = os.homedir();
const code2promptDir = path.join(homeDir, 'code2prompt');
if (!fs.existsSync(code2promptDir)) {
fs.mkdirSync(code2promptDir, { recursive: true });
}
const originalWorkingDir = process.cwd();
process.chdir(code2promptDir);
process.env.REQ_TIMEOUT = '3600000'; // set pythonia timeout to 1 hour
const { python } = require('pythonia');
// Load the Python helper script
const py = await python('./python_runner.py'); // Ensure the file path is correct
process.chdir(originalWorkingDir); // restore original cwd
// If context specifies packages to install, install them first
if (context.packages && Array.isArray(context.packages)) {
for (const pkg of context.packages) {
const installResult = await py.install_package(pkg);
this.x_console.out({ color:'cyan', message:`Package install log: ${installResult}` });
}
}
// Convert context to JSON for passing into Python
const contextJson = JSON.stringify(context);
// Run the Python code
const resultJson = await py.run_python_code(code, contextJson);
// Parse the returned JSON from Python
let result;
try {
result = JSON.parse(resultJson);
if (result.result) result = result.result;
//console.log('returned from python:', result);
} catch (err) {
//result = { error: 'Failed to parse Python output as JSON', details: err.toString() };
results = {};
}
// Close the Python interpreter
await python.exit();
return result;
}
async executeNode(context=null,code=null) {
// context=object with variables returned by previous code blocks
const prompts = require('prompts');
let wAsync = `(async function() {
${code}
})();\n`;
const self = this;
// returns methods,vars available within the code blocks contexts
let context_ = {
process,
z,
console: {
log: function(message,data) {
self.x_console.setColorTokens({
'*':'yellow',
'#':'cyan',
'@':'green'
});
self.x_console.out({ color:'cyan', message:self.x_console.colorize(message), data });
},
},
prompt: async(question='',validation=null)=>{
const resp = (
await prompts({
type: 'text',
name: 'value',
message: this.x_console.colorize(question),
validate: (value) => {
if (validation) return validation(value);
return true
}
})
).value;
return resp;
}
};
if (context) {
context_ = {...context_,...context};
}
// execute code block on an isolated async context
this.lastEval = wAsync;
let tmp = await safeEval(wAsync, context_);
return tmp;
//
}
async spawnBash(context = {}, code=null) {
const { spawn } = require('child_process');
if (!code) {
throw new Error("Command must not be empty");
}
return new Promise((resolve, reject) => {
// simplify context to only string, number, boolean
const simpleContext = Object.keys(context).reduce((acc, key) => {
const value = context[key];
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
acc[key] = value;
}
return acc;
}, {});
// Handle command with arguments that might include spaces
const shell = process.platform === 'win32' ? { cmd: 'cmd', arg: '/C' } : { cmd: 'sh', arg: '-c' };
const shellOptions = {
env: {
...process.env,
...simpleContext,
CI: 'true',
npm_config_yes: 'yes',
CONTINUOUS_INTEGRATION: 'true'
},
shell: true,
cwd: this.currentFolder
};
//console.log("Executing command:", shell.cmd, shell.arg, code);
//console.log("Environment PATH:", shellOptions.env.PATH);
const proc = spawn(shell.cmd, [shell.arg, code], shellOptions);
let output = ''; // To capture the output
proc.stdout.on('data', (data) => {
output += data.toString(); // Append real-time output
});
proc.stderr.on('data', (data) => {
output += data.toString(); // Capture stderr in the output
});
proc.on('error', (err) => {
reject(err);
});
proc.on('close', (code_) => {
if (code_ === 0) {
resolve(output);
} else {
reject(new Error(`Process exited with code ${code_}: ${output}`));
}
});
});
}
async executeBash(context = {}, code = null) {
if (!code) {
throw new Error("No code provided for execution");
}
// Replace placeholders in the code with context values
const processedCode = typeof code === 'string' ? code.replace(/\{(.*?)\}/g, (match, key) => {
if (context[key] !== undefined) {
return context[key];
}
//throw new Error(`Key ${key} not found in context`);
}) : '';
// Append command to output all exported variables (you need to ensure this doesn't break your script)
//let exportCmd = "\nexport -p"; // This line outputs all exported variables in the format 'declare -x KEY="value"'
let exportCmd = ""; // This line outputs all exported variables in the format 'declare -x KEY="value"'
let fullScript = processedCode + exportCmd;
// Set up the environment for non-interactive execution
const simpleContext = Object.keys(context).reduce((acc, key) => {
const value = context[key];
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
acc[key] = value;
}
return acc;
}, {});
const environment = {
...process.env,
...simpleContext,
npm_config_yes: 'yes', // Set npm to non-interactive mode
npx_config_yes: 'yes', // Set npm to non-interactive mode
CI: 'true', // Adding the CI environment variable
CONTINUOUS_INTEGRATION: 'true' // Adding another common CI environment variable
};
try {
const { stdout, stderr } = await execAsync(fullScript, {
shell: '/bin/bash',
env: environment,
cwd: this.currentFolder // Set the current working directory to this.currentFolder
});
if (stderr) {
//console.error('Error executing bash:', stderr);
return { output: stdout, error: stderr };
}
return { output: stdout };
} catch (error) {
//console.error('Execution error:', error);
throw error;
}
}
}
module.exports = codeBlocks;