2024-01-03 22:16:50 -08:00
import { writeFile , readFile , mkdirSync } from 'fs' ;
2023-11-07 12:00:55 -06:00
export class Coder {
constructor ( agent ) {
this . agent = agent ;
2023-11-07 12:46:55 -06:00
this . file _counter = 0 ;
2023-12-21 15:11:38 -07:00
this . fp = '/bots/' + agent . name + '/action-code/' ;
2023-11-13 00:57:20 -06:00
this . executing = false ;
2024-01-26 15:41:55 -06:00
this . generating = false ;
2023-11-12 13:57:22 -06:00
this . code _template = '' ;
2023-12-06 22:15:09 -06:00
this . timedout = false ;
2023-11-12 13:57:22 -06:00
2023-12-21 15:11:38 -07:00
readFile ( './bots/template.js' , 'utf8' , ( err , data ) => {
2023-11-12 13:57:22 -06:00
if ( err ) throw err ;
this . code _template = data ;
} ) ;
2023-12-21 20:42:01 -07:00
mkdirSync ( '.' + this . fp , { recursive : true } ) ;
2023-11-07 12:00:55 -06:00
}
2024-01-15 12:04:18 -06:00
// write custom code to file and import it
async stageCode ( code ) {
2024-02-16 11:57:48 -06:00
code = this . sanitizeCode ( code ) ;
2024-01-15 12:04:18 -06:00
let src = '' ;
code = code . replaceAll ( 'console.log(' , 'log(bot,' ) ;
code = code . replaceAll ( 'log("' , 'log(bot,"' ) ;
// this may cause problems in callback functions
code = code . replaceAll ( ';\n' , '; if(bot.interrupt_code) {log(bot, "Code interrupted.");return;}\n' ) ;
for ( let line of code . split ( '\n' ) ) {
src += ` ${ line } \n ` ;
}
src = this . code _template . replace ( '/* CODE HERE */' , src ) ;
console . log ( "writing to file..." , src )
let filename = this . file _counter + '.js' ;
// if (this.file_counter > 0) {
// let prev_filename = this.fp + (this.file_counter-1) + '.js';
// unlink(prev_filename, (err) => {
// console.log("deleted file " + prev_filename);
// if (err) console.error(err);
// });
// } commented for now, useful to keep files for debugging
this . file _counter ++ ;
let write _result = await this . writeFilePromise ( '.' + this . fp + filename , src )
if ( write _result ) {
console . error ( 'Error writing code execution file: ' + result ) ;
return null ;
}
return await import ( '../..' + this . fp + filename ) ;
2023-11-08 19:24:24 -06:00
}
2024-02-16 11:57:48 -06:00
sanitizeCode ( code ) {
code = code . trim ( ) ;
2024-01-26 15:05:36 -06:00
const remove _strs = [ 'Javascript' , 'javascript' , 'js' ]
2023-11-08 19:24:24 -06:00
for ( let r of remove _strs ) {
if ( code . startsWith ( r ) ) {
code = code . slice ( r . length ) ;
return code ;
}
}
return code ;
2023-11-07 12:00:55 -06:00
}
2023-11-07 12:46:55 -06:00
writeFilePromise ( filename , src ) {
// makes it so we can await this function
return new Promise ( ( resolve , reject ) => {
writeFile ( filename , src , ( err ) => {
if ( err ) {
reject ( err ) ;
} else {
resolve ( ) ;
}
} ) ;
} ) ;
}
2024-01-03 22:16:50 -08:00
async generateCode ( agent _history ) {
2024-01-26 15:41:55 -06:00
// wrapper to prevent overlapping code generation loops
await this . stop ( ) ;
this . generating = true ;
2024-01-26 14:10:09 -08:00
let res = await this . generateCodeLoop ( agent _history ) ;
2024-01-26 15:41:55 -06:00
this . generating = false ;
2024-01-26 14:10:09 -08:00
if ( ! res . interrupted ) this . agent . bot . emit ( 'idle' ) ;
2024-02-16 11:57:48 -06:00
return res . message ;
2024-01-26 15:41:55 -06:00
}
async generateCodeLoop ( agent _history ) {
2024-02-25 14:13:32 -06:00
let messages = agent _history . getHistory ( ) ;
2024-01-03 22:16:50 -08:00
2024-01-09 13:42:39 -06:00
let code _return = null ;
let failures = 0 ;
for ( let i = 0 ; i < 5 ; i ++ ) {
2024-01-26 15:05:36 -06:00
if ( this . agent . bot . interrupt _code )
2024-02-16 11:57:48 -06:00
return { success : true , message : null , interrupted : true , timedout : false } ;
2024-01-09 13:42:39 -06:00
console . log ( messages )
2024-02-25 14:13:32 -06:00
let res = await this . agent . prompter . promptCoding ( messages ) ;
2024-01-09 13:42:39 -06:00
let contains _code = res . indexOf ( '```' ) !== - 1 ;
if ( ! contains _code ) {
2024-03-23 11:15:53 -05:00
if ( res . indexOf ( '!newAction' ) !== - 1 ) {
messages . push ( {
role : 'assistant' ,
content : res . substring ( 0 , res . indexOf ( '!newAction' ) )
} ) ;
continue ; // using newaction will continue the loop
}
2024-01-09 13:42:39 -06:00
if ( code _return ) {
agent _history . add ( 'system' , code _return . message ) ;
agent _history . add ( this . agent . name , res ) ;
this . agent . bot . chat ( res ) ;
2024-01-26 14:10:09 -08:00
return { success : true , message : null , interrupted : false , timedout : false } ;
2024-01-09 13:42:39 -06:00
}
if ( failures >= 1 ) {
2024-02-16 11:57:48 -06:00
return { success : false , message : 'Action failed, agent would not write code.' , interrupted : false , timedout : false } ;
2024-01-09 13:42:39 -06:00
}
messages . push ( {
role : 'system' ,
content : 'Error: no code provided. Write code in codeblock in your response. ``` // example ```' }
) ;
failures ++ ;
continue ;
}
2024-01-03 22:16:50 -08:00
let code = res . substring ( res . indexOf ( '```' ) + 3 , res . lastIndexOf ( '```' ) ) ;
2024-01-15 12:04:18 -06:00
const execution _file = await this . stageCode ( code ) ;
if ( ! execution _file ) {
agent _history . add ( 'system' , 'Failed to stage code, something is wrong.' ) ;
2024-01-26 14:10:09 -08:00
return { success : false , message : null , interrupted : false , timedout : false } ;
2024-01-15 12:04:18 -06:00
}
code _return = await this . execute ( async ( ) => {
return await execution _file . main ( this . agent . bot ) ;
} ) ;
2024-01-03 22:16:50 -08:00
2024-01-09 13:42:39 -06:00
if ( code _return . interrupted && ! code _return . timedout )
2024-01-26 14:10:09 -08:00
return { success : false , message : null , interrupted : true , timedout : false } ;
2024-02-16 11:57:48 -06:00
console . log ( "Code generation result:" , code _return . success , code _return . message ) ;
2024-01-03 22:16:50 -08:00
messages . push ( {
role : 'assistant' ,
content : res
} ) ;
messages . push ( {
2024-01-09 13:42:39 -06:00
role : 'system' ,
2024-01-03 22:16:50 -08:00
content : code _return . message
} ) ;
}
2024-01-26 14:10:09 -08:00
return { success : false , message : null , interrupted : false , timedout : true } ;
2024-01-03 22:16:50 -08:00
}
2023-11-13 00:57:20 -06:00
2024-02-02 15:34:17 -06:00
async executeResume ( func = null , name = null , timeout = 10 ) {
2024-01-26 12:11:32 -08:00
if ( func != null ) {
2024-02-02 15:34:17 -06:00
this . resume _func = func ;
this . resume _name = name ;
2024-01-26 12:11:32 -08:00
}
2024-02-03 16:19:20 -08:00
if ( this . resume _func != null && this . agent . isIdle ( ) ) {
2024-03-05 12:50:13 -08:00
console . log ( 'resuming code...' )
2024-01-26 12:11:32 -08:00
this . interruptible = true ;
2024-02-02 15:34:17 -06:00
let res = await this . execute ( this . resume _func , timeout ) ;
2024-01-26 12:11:32 -08:00
this . interruptible = false ;
return res ;
} else {
return { success : false , message : null , interrupted : false , timedout : false } ;
}
}
2024-02-05 13:21:32 -06:00
cancelResume ( ) {
this . resume _func = null ;
this . resume _name = null ;
}
2023-12-08 16:18:20 -06:00
// returns {success: bool, message: string, interrupted: bool, timedout: false}
2024-01-16 15:53:27 -06:00
async execute ( func , timeout = 10 ) {
2023-12-06 22:15:09 -06:00
if ( ! this . code _template ) return { success : false , message : "Code template not loaded." , interrupted : false , timedout : false } ;
2023-12-04 21:33:40 -06:00
2023-12-10 20:19:07 -06:00
let TIMEOUT ;
2023-11-07 12:46:55 -06:00
try {
console . log ( 'executing code...\n' ) ;
2023-11-13 00:57:20 -06:00
await this . stop ( ) ;
2024-01-15 12:04:18 -06:00
this . clear ( ) ;
2023-11-15 17:00:02 -06:00
2023-11-08 21:05:18 -08:00
this . executing = true ;
2024-01-15 12:04:18 -06:00
if ( timeout > 0 )
TIMEOUT = this . _startTimeout ( timeout ) ;
await func ( ) ; // open fire
2023-11-08 21:05:18 -08:00
this . executing = false ;
2023-12-10 20:19:07 -06:00
clearTimeout ( TIMEOUT ) ;
2023-12-09 21:40:53 -06:00
2023-11-13 00:57:20 -06:00
let output = this . formatOutput ( this . agent . bot ) ;
2023-11-13 11:09:37 -06:00
let interrupted = this . agent . bot . interrupt _code ;
2023-12-06 22:15:09 -06:00
let timedout = this . timedout ;
2023-11-07 22:23:42 -06:00
this . clear ( ) ;
2024-01-26 14:10:09 -08:00
if ( ! interrupted && ! this . generating ) this . agent . bot . emit ( 'idle' ) ;
2023-12-06 22:15:09 -06:00
return { success : true , message : output , interrupted , timedout } ;
2023-11-07 12:46:55 -06:00
} catch ( err ) {
2024-01-26 15:05:36 -06:00
this . executing = false ;
2024-01-25 15:52:07 -08:00
clearTimeout ( TIMEOUT ) ;
2024-02-05 13:21:32 -06:00
this . cancelResume ( ) ;
2024-01-26 15:05:36 -06:00
console . error ( "Code execution triggered catch: " + err ) ;
2024-01-15 12:04:18 -06:00
await this . stop ( ) ;
2024-01-25 15:52:07 -08:00
let message = this . formatOutput ( this . agent . bot ) + '!!Code threw exception!! Error: ' + err ;
2023-11-13 11:09:37 -06:00
let interrupted = this . agent . bot . interrupt _code ;
2024-01-15 12:04:18 -06:00
this . clear ( ) ;
2024-01-26 14:10:09 -08:00
if ( ! interrupted && ! this . generating ) this . agent . bot . emit ( 'idle' ) ;
2023-12-06 22:15:09 -06:00
return { success : false , message , interrupted , timedout : false } ;
2023-11-07 12:46:55 -06:00
}
2023-11-07 12:00:55 -06:00
}
2023-11-07 22:23:42 -06:00
2023-11-13 00:57:20 -06:00
formatOutput ( bot ) {
2023-12-08 16:18:20 -06:00
if ( bot . interrupt _code && ! this . timedout ) return '' ;
2023-11-13 00:57:20 -06:00
let output = bot . output ;
2024-02-16 11:57:48 -06:00
const MAX _OUT = 500 ;
2023-11-13 00:57:20 -06:00
if ( output . length > MAX _OUT ) {
output = ` Code output is very long ( ${ output . length } chars) and has been shortened. \n
First outputs : \ n$ { output . substring ( 0 , MAX _OUT / 2 ) } \ n ... skipping many lines . \ nFinal outputs : \ n $ { output . substring ( output . length - MAX _OUT / 2 ) } ` ;
}
else {
output = 'Code output:\n' + output ;
}
return output ;
2023-11-12 13:57:22 -06:00
}
2023-11-13 00:57:20 -06:00
async stop ( ) {
2023-12-06 22:15:09 -06:00
if ( ! this . executing ) return ;
2024-02-03 12:00:33 -06:00
const start = Date . now ( ) ;
2023-11-08 21:05:18 -08:00
while ( this . executing ) {
2023-11-13 11:09:37 -06:00
this . agent . bot . interrupt _code = true ;
2023-11-08 21:05:18 -08:00
this . agent . bot . collectBlock . cancelTask ( ) ;
this . agent . bot . pathfinder . stop ( ) ;
2023-11-27 21:07:52 -06:00
this . agent . bot . pvp . stop ( ) ;
2024-02-03 12:00:33 -06:00
console . log ( 'waiting for code to finish executing...' ) ;
2023-11-12 17:41:01 -06:00
await new Promise ( resolve => setTimeout ( resolve , 1000 ) ) ;
2024-02-03 12:00:33 -06:00
if ( Date . now ( ) - start > 10 * 1000 ) {
process . exit ( 1 ) ; // force exit program after 10 seconds of failing to stop
}
2023-11-08 21:05:18 -08:00
}
2023-11-13 00:57:20 -06:00
}
clear ( ) {
this . agent . bot . output = '' ;
2023-11-13 11:09:37 -06:00
this . agent . bot . interrupt _code = false ;
2023-12-06 22:15:09 -06:00
this . timedout = false ;
}
_startTimeout ( TIMEOUT _MINS = 10 ) {
return setTimeout ( async ( ) => {
console . warn ( ` Code execution timed out after ${ TIMEOUT _MINS } minutes. Attempting force stop. ` ) ;
this . timedout = true ;
2023-12-08 16:18:20 -06:00
this . agent . bot . output += ` \n Action performed for ${ TIMEOUT _MINS } minutes and then timed out and stopped. You may want to continue or do something else. ` ;
2023-12-06 22:15:09 -06:00
this . stop ( ) ; // last attempt to stop
2023-12-08 16:18:20 -06:00
await new Promise ( resolve => setTimeout ( resolve , 5 * 1000 ) ) ; // wait 5 seconds
2023-12-06 22:15:09 -06:00
if ( this . executing ) {
console . error ( ` Failed to stop. Killing process. Goodbye. ` ) ;
2023-12-09 21:40:53 -06:00
this . agent . bot . output += ` \n Force stop failed! Process was killed and will be restarted. Goodbye world. ` ;
2023-12-12 13:39:35 -06:00
this . agent . bot . chat ( 'Goodbye world.' ) ;
2023-12-08 16:18:20 -06:00
let output = this . formatOutput ( this . agent . bot ) ;
this . agent . history . add ( 'system' , output ) ;
this . agent . history . save ( ) ;
2023-12-06 22:15:09 -06:00
process . exit ( 1 ) ; // force exit program
}
console . log ( 'Code execution stopped successfully.' ) ;
} , TIMEOUT _MINS * 60 * 1000 ) ;
2023-11-07 22:23:42 -06:00
}
2023-11-07 12:00:55 -06:00
}