diff --git a/cli2.js b/cli2.js index 75645c2..fd75d77 100644 --- a/cli2.js +++ b/cli2.js @@ -1,12 +1,19 @@ import ModelDialog from './modelDialog.js'; +import chalk from 'chalk'; + + + + + + const modelDialog = new ModelDialog(); modelDialog.on('outputUpdate', (output) => { - console.log('output',output); + console.log(chalk.blue('output event'),output); }); modelDialog.on('reasoningUpdate', (output) => { - console.log('reasoning',output); + console.log(chalk.blue('reasoning event'),output); }); @@ -15,7 +22,9 @@ modelDialog.on('reasoningUpdate', (output) => { (async ()=>{ const output = await modelDialog.interrogate('Can you remember "seven" ?'); - console.log(output.output,output.reasoning); - const output2 = await modelDialog.interrogate('And what comes after that?'); - console.log(output2.output,output2.reasoning,output2.inputTokens,output2.cachedTokens,output2.outputTokens); + console.log(output.output,JSON.stringify(output.reasoning,null,2)); + const output2 = await modelDialog.interrogate('read a file that is what you remebered plus 1 as a word with txt ending, check that file.'); + console.log('final output:',output2.output); + console.log('reasoning:',output2.reasoning); + console.log('Tokens:',output2.inputTokens,output2.cachedTokens,output2.outputTokens); })() diff --git a/modelDialog.js b/modelDialog.js index 3c4a7f2..51b6bb1 100644 --- a/modelDialog.js +++ b/modelDialog.js @@ -4,6 +4,7 @@ import EventEmitter from 'events'; import path from 'path'; import fs from 'fs/promises'; import { fileURLToPath } from 'node:url'; +import chalk from 'chalk'; async function loadTools() { const __dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -43,6 +44,7 @@ if (!Array.fromAsync) { class ModelDialog { constructor() { this.messages = [systemprompt]; + this.messagesSent = []; this.isActive = false; this.currentStream = null; this.previousResponseId = null; @@ -79,54 +81,99 @@ class ModelDialog { this.messages.push({"role": "user", "content": [ {"type": "input_text","text": prompt }]}); - const call = { - model: 'gpt-5-nano', - input: structuredClone(this.messages), - text: { format: { type: 'text' }, verbosity: 'low' }, - reasoning: { effort: 'medium', summary: 'detailed' }, - tools: Object.values(toolsByFile).map(t => t.def), - store: true, - previous_response_id: this.previousResponseId - } + const outputs = []; - this.currentStream = await openai.responses.stream(call); - this.currentStream.on('response.created', (event) => { - this.previousResponseId = event.response.id; - }); + do{ + const messagesToSend = this.messages.splice(0); + this.messagesSent.push(...messagesToSend); - const deltas = []; - this.currentStream.on('response.output_text.delta', (event) => { - deltas.push(event.delta); - this.emitter.emit('outputUpdate', deltas.join('')); - }); + const call = { + model: 'gpt-5-nano', + input: messagesToSend, + text: { format: { type: 'text' }, verbosity: 'low' }, + reasoning: { effort: 'medium', summary: 'detailed' }, + tools: Object.values(toolsByFile).map(t => t.def), + store: true, + previous_response_id: this.previousResponseId + } - const reasoningDeltas = []; - this.currentStream.on('response.reasoning_summary_text.delta', (event) => { - if(!reasoningDeltas[event.summary_index]) reasoningDeltas[event.summary_index] = []; - reasoningDeltas[event.summary_index].push(event.delta); - this.emitter.emit('reasoningUpdate', reasoningDeltas[event.summary_index].join('')); - }); + this.currentStream = openai.responses.stream(call); + this.currentStream.on('response.created', (event) => { + this.previousResponseId = event.response.id; + }); - this.currentStream.on('response.reasoning_summary_text.done', (event) => { - //console.log(event); - }); + const deltas = []; + this.currentStream.on('response.output_text.delta', (event) => { + deltas.push(event.delta); + this.emitter.emit('outputUpdate', deltas.join('')); + }); + + const reasoningDeltas = []; + this.currentStream.on('response.reasoning_summary_text.delta', (event) => { + if(!reasoningDeltas[event.summary_index]) reasoningDeltas[event.summary_index] = []; + reasoningDeltas[event.summary_index].push(event.delta); + this.emitter.emit('reasoningUpdate', reasoningDeltas[event.summary_index].join('')); + }); + + this.currentStream.on('response.reasoning_summary_text.done', (event) => { + //console.log(event); + }); + this.currentStream.on('response.function_call_arguments.delta', (event) => { + process.stdout.write(chalk.yellow(event.delta)); + }); + this.currentStream.on('response.function_call_arguments.done', (event) => { + process.stdout.write("\n"); + }); + this.currentStream.on('response.completed', async (event) => { + this.handleUsage(event.response.usage, event.response.model); + outputs.push(...event.response.output); - let output; - this.currentStream.on('response.completed', async (event) => { - this.handleUsage(event.response.usage, event.response.model); - output = event.response.output; - }); + for(const toolCall of event.response.output.filter(i => i.type === 'function_call')){ + // Limit the 'arguments' field to 400 characters for logging + const limitedArgs = typeof toolCall.arguments === 'string' + ? (toolCall.arguments.length > 400 ? toolCall.arguments.slice(0, 400) + '...[truncated]' : toolCall.arguments) + : toolCall.arguments; + console.log( + chalk.green('tool call:'), + { ...toolCall, arguments: limitedArgs } + ); + const tool = toolsByFile[toolCall.name]; + let args; + try{ + args = JSON.parse(toolCall.arguments); + } catch(e){ + console.error(chalk.red('Error parsing arguments:'), e, toolCall.arguments); + this.messages.push({ + type: "function_call_output", + call_id: toolCall.call_id, + output: {error: 'Exception in parsing arguments', exception: e}, + }); + continue; + } + const result = await tool.run(args); + console.log(chalk.green('function call result:'),'',toolCall.name,'\n','',args,'\n','',result,''); + this.messages.push({ + type: "function_call_output", + call_id: toolCall.call_id, + output: JSON.stringify(result), + }); + } + }); + + await Array.fromAsync(this.currentStream); + + console.log(chalk.green('Do we need to loop? messages in array = '),this.messages.length) + } while(this.messages.length > 0); - await Array.fromAsync(this.currentStream); this.isActive = false; - const now = Date.now(); - this.lastDebouncedUpdate = now; + this.lastDebouncedUpdate = 0; return { - output: output.filter(i => i.type === 'message').map(i => i.content[0].text).join('\n') , - reasoning: reasoningDeltas.map(i => i.join('')), - inputTokens: this.inputTokens, outputTokens: this.outputTokens, cachedTokens: this.cachedTokens}; + output: outputs.filter(i => i.type === 'message').map(i => i.content[0].text) , + reasoning: outputs.filter(i => i.type === 'reasoning').map(i => i.summary.map(j => j.text).join('\n')), + inputTokens: this.inputTokens, outputTokens: this.outputTokens, cachedTokens: this.cachedTokens + }; } } diff --git a/package-lock.json b/package-lock.json index 3e76790..78a4ba5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "chalk": "^5.5.0", "dotenv": "^17.2.1", "exa-js": "^1.8.27", "ink": "^6.1.0", diff --git a/package.json b/package.json index 877185d..cf4b7a9 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "license": "ISC", "description": "", "dependencies": { + "chalk": "^5.5.0", "dotenv": "^17.2.1", "exa-js": "^1.8.27", "ink": "^6.1.0", diff --git a/tools/list_files.js b/tools/list_files.js index 7c5c97d..cdaac44 100644 --- a/tools/list_files.js +++ b/tools/list_files.js @@ -120,7 +120,7 @@ export async function run(args) { const inputPath = args?.path || ""; const depth = Number.isInteger(args?.depth) ? args.depth : 1; const includeHidden = args?.includeHidden ?? false; - const chrootPath = '/workspaces/aiTools'; + const chrootPath = '/workspaces/aiTools/root'; if (!chrootPath) { return { err: "Chroot path is required" }; diff --git a/tools/patch_files.js b/tools/patch_files.js index 943c491..8dc1eda 100644 --- a/tools/patch_files.js +++ b/tools/patch_files.js @@ -808,7 +808,7 @@ export async function run(args) { open_file, write_file, remove_file, - '/workspaces/aiTools' + '/workspaces/aiTools/root' ); return result; } catch (error) { diff --git a/tools/read_file.js b/tools/read_file.js index 0269633..0285a70 100644 --- a/tools/read_file.js +++ b/tools/read_file.js @@ -3,7 +3,7 @@ import { createInterface } from "node:readline"; import path from "node:path"; -const virtual_chroot = '/workspaces/aiTools'; +const virtual_chroot = '/workspaces/aiTools/root'; // Ensures reads are confined to `virtual_chroot`. @@ -13,7 +13,7 @@ export default { type: "object", required: ["path","linesToSkip","linesToRead"], additionalProperties: false, properties: { path: { type: "string", description: "The path to the file to read.", }, linesToSkip: { type: "integer", description: "The number of lines to skip. Use 0 to read from the beginning.", minimum: 0 }, - linesToRead: { type: "integer", description: "1-400 The number of lines to read. Use 0 or more than 400 to read 400 lines.", minimum: 0 } + linesToRead: { type: "integer", description: "1-400 The number of lines to read.", minimum: 1, maximum: 400 } } } }; @@ -29,6 +29,8 @@ export async function run(args) { // Normalize linesToRead (1-400, with 0 or >400 meaning 400) const maxLines = (linesToRead <= 0 || linesToRead > 400) ? 400 : linesToRead; + + // return 'FILE DOES NOT EXIST' try { diff --git a/tools/ripgrep.js b/tools/ripgrep.js index 277a618..a814914 100644 --- a/tools/ripgrep.js +++ b/tools/ripgrep.js @@ -1,6 +1,6 @@ import { spawnSync } from "node:child_process"; -const virtual_chroot = '/workspaces/aiTools'; +const virtual_chroot = '/workspaces/aiTools/root'; export default { type: "function", name: "ripgrep", strict: true, diff --git a/tools/wget.js b/tools/wget.js index dab99ae..11d9d84 100644 --- a/tools/wget.js +++ b/tools/wget.js @@ -7,7 +7,10 @@ export async function run(args){ const buffer = await res.buffer(); const filename = new Date().getTime() + '.' + url.split('.').pop(); const content = buffer.slice(0, 500).toString('utf8'); - return { filename, content }; + // save the file to the chroot + const filePath = `/workspaces/aiTools/root/${filename}`; + fs.writeFileSync(filePath, content); + return { 'Downloaded to:': filename }; }; // metadata for the tool runner