From 657b6af993b69b130c7ee1613b7dce3de1458cb4 Mon Sep 17 00:00:00 2001 From: sebseb7 Date: Thu, 14 Aug 2025 09:41:17 +0000 Subject: [PATCH] Enhance output logging in CLI and ModelDialog by integrating chalk for better readability. Update output handling to include detailed reasoning and token information. Refactor message management in ModelDialog to improve response processing and add support for function call arguments. Adjust chroot paths in various tools for consistency. --- cli2.js | 19 +++++-- modelDialog.js | 123 ++++++++++++++++++++++++++++++------------- package-lock.json | 1 + package.json | 1 + tools/list_files.js | 2 +- tools/patch_files.js | 2 +- tools/read_file.js | 6 ++- tools/ripgrep.js | 2 +- tools/wget.js | 5 +- 9 files changed, 112 insertions(+), 49 deletions(-) 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