2024-01-03 22:16:50 -08:00
import { writeFile , readFile , mkdirSync } from 'fs' ;
2024-01-15 11:04:50 -06:00
import { sendRequest } from '../utils/gpt.js' ;
2024-01-09 13:42:39 -06:00
import { getSkillDocs } from './skill-library.js' ;
2024-01-15 11:04:50 -06:00
import { Examples } from './examples.js' ;
2024-01-03 22:16:50 -08:00
2023-11-07 12:00:55 -06:00
export class Coder {
constructor ( agent ) {
this . agent = agent ;
2023-11-15 17:00:02 -06:00
this . queued _code = '' ;
2023-11-07 12:00:55 -06:00
this . current _code = '' ;
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 ;
2023-11-12 13:57:22 -06:00
this . code _template = '' ;
2023-12-06 22:15:09 -06:00
this . timedout = false ;
2024-01-15 11:04:50 -06:00
}
async load ( ) {
this . examples = new Examples ( ) ;
await this . examples . load ( './src/examples_coder.json' ) ;
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
}
queueCode ( code ) {
2023-11-15 17:00:02 -06:00
this . queued _code = this . santitizeCode ( code ) ;
2023-11-08 19:24:24 -06:00
}
santitizeCode ( code ) {
const remove _strs = [ 'javascript' , 'js' ]
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-09 13:42:39 -06:00
let system _message = "You are a minecraft bot that plays minecraft by writing javascript codeblocks. Given the conversation between you and the user, use the provided skills and world queries to write your code in a codeblock. Example response: ``` // your code here ``` You will then be given a response to your code. If you are satisfied with the response, respond without a codeblock in a conversational way. If something went wrong, write another codeblock and try to fix the problem." ;
2024-01-03 22:16:50 -08:00
system _message += getSkillDocs ( ) ;
2024-01-09 13:42:39 -06:00
system _message += "\n\nExamples:\nUser zZZn98: come here \nAssistant: I am going to navigate to zZZn98. ```\nawait skills.goToPlayer(bot, 'zZZn98');```\nSystem: Code execution finished successfully.\nAssistant: Done." ;
2024-01-03 22:16:50 -08:00
2024-01-15 11:04:50 -06:00
let messages = await agent _history . getHistory ( this . examples ) ;
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 ++ ) {
console . log ( messages )
2024-01-03 22:16:50 -08:00
let res = await sendRequest ( messages , system _message ) ;
2024-01-09 13:42:39 -06:00
console . log ( 'Code generation response:' , res )
let contains _code = res . indexOf ( '```' ) !== - 1 ;
if ( ! contains _code ) {
if ( code _return ) {
agent _history . add ( 'system' , code _return . message ) ;
agent _history . add ( this . agent . name , res ) ;
this . agent . bot . chat ( res ) ;
return ;
}
if ( failures >= 1 ) {
agent _history . add ( 'system' , 'Action failed, agent would not write code.' ) ;
return ;
}
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-09 13:42:39 -06:00
this . queueCode ( code ) ;
code _return = await this . execute ( ) ;
2024-01-03 22:16:50 -08:00
2024-01-09 13:42:39 -06:00
if ( code _return . interrupted && ! code _return . timedout )
return ;
2024-01-13 12:10:15 -06:00
console . log ( 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-09 13:42:39 -06:00
return
2024-01-03 22:16:50 -08:00
}
2023-11-13 00:57:20 -06:00
2023-12-08 16:18:20 -06:00
// returns {success: bool, message: string, interrupted: bool, timedout: false}
2023-11-07 12:00:55 -06:00
async execute ( ) {
2023-12-06 22:15:09 -06:00
if ( ! this . queued _code ) return { success : false , message : "No code to execute." , interrupted : false , timedout : false } ;
if ( ! this . code _template ) return { success : false , message : "Code template not loaded." , interrupted : false , timedout : false } ;
2023-11-12 13:57:22 -06:00
let src = '' ;
2023-12-04 21:33:40 -06:00
let code = this . queued _code ;
code = code . replaceAll ( 'console.log(' , 'log(bot,' ) ;
code = code . replaceAll ( 'log("' , 'log(bot,"' ) ;
2023-11-15 17:00:02 -06:00
// this may cause problems in callback functions
2023-12-04 21:33:40 -06:00
code = code . replaceAll ( ';\n' , '; if(bot.interrupt_code) {log(bot, "Code interrupted.");return;}\n' ) ;
2023-11-15 17:00:02 -06:00
for ( let line of code . split ( '\n' ) ) {
2023-11-07 12:00:55 -06:00
src += ` ${ line } \n ` ;
}
2023-11-12 13:57:22 -06:00
src = this . code _template . replace ( '/* CODE HERE */' , src ) ;
2023-11-07 12:00:55 -06:00
console . log ( "writing to file..." , src )
2023-11-07 12:46:55 -06:00
2023-12-21 15:11:38 -07:00
let filename = this . file _counter + '.js' ;
2023-11-08 19:24:24 -06:00
// 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
2023-11-07 12:46:55 -06:00
this . file _counter ++ ;
2023-12-21 15:11:38 -07:00
let write _result = await this . writeFilePromise ( '.' + this . fp + filename , src )
2023-11-07 12:00:55 -06:00
2023-11-08 19:24:24 -06:00
if ( write _result ) {
2023-11-07 12:46:55 -06:00
console . error ( 'Error writing code execution file: ' + result ) ;
2023-12-06 22:15:09 -06:00
return { success : false , message : result , interrupted : false , timedout : false } ;
2023-11-07 12:46:55 -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-12-21 15:11:38 -07:00
let execution _file = await import ( '../..' + this . fp + filename ) ;
2023-11-13 00:57:20 -06:00
await this . stop ( ) ;
2023-11-15 17:00:02 -06:00
this . current _code = this . queued _code ;
2023-11-08 21:05:18 -08:00
this . executing = true ;
2023-12-12 13:39:35 -06:00
TIMEOUT = this . _startTimeout ( 10 ) ;
2023-12-06 22:15:09 -06:00
await execution _file . main ( this . agent . bot ) ; // 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 ( ) ;
2023-12-20 16:30:05 -08:00
this . agent . bot . emit ( "code_terminated" ) ;
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 ) {
2023-11-08 21:05:18 -08:00
this . executing = false ;
2023-12-10 20:19:07 -06:00
clearTimeout ( TIMEOUT ) ;
console . error ( "Code execution triggered catch: " + err ) ;
2023-11-13 00:57:20 -06:00
let message = this . formatOutput ( this . agent . bot ) ;
2023-11-12 13:57:22 -06:00
message += '!!Code threw exception!! Error: ' + err ;
2023-11-13 11:09:37 -06:00
let interrupted = this . agent . bot . interrupt _code ;
2023-11-13 00:57:20 -06:00
await this . stop ( ) ;
2023-12-20 16:30:05 -08:00
this . agent . bot . emit ( "code_terminated" ) ;
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 ;
const MAX _OUT = 1000 ;
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 ;
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 ( ) ;
2023-11-13 11:09:37 -06:00
console . log ( 'waiting for code to finish executing... interrupt:' , this . agent . bot . interrupt _code ) ;
2023-11-12 17:41:01 -06:00
await new Promise ( resolve => setTimeout ( resolve , 1000 ) ) ;
2023-11-08 21:05:18 -08:00
}
2023-11-12 13:57:22 -06:00
this . clear ( ) ;
2023-11-13 00:57:20 -06:00
}
clear ( ) {
2023-11-07 22:23:42 -06:00
this . current _code = '' ;
2023-11-13 00:57:20 -06:00
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
}