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.
This commit is contained in:
19
cli2.js
19
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);
|
||||
})()
|
||||
|
||||
123
modelDialog.js
123
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>',toolCall.name,'</toolCall.name>\n','<args>',args,'</args>\n','<result>',result,'</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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1
package-lock.json
generated
1
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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" };
|
||||
|
||||
@@ -808,7 +808,7 @@ export async function run(args) {
|
||||
open_file,
|
||||
write_file,
|
||||
remove_file,
|
||||
'/workspaces/aiTools'
|
||||
'/workspaces/aiTools/root'
|
||||
);
|
||||
return result;
|
||||
} catch (error) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user