feat: render function calls

This commit is contained in:
Shivering Ocelot committed 2026-10-10 18:25:40 -05:00
1 parent 4d878dfb61
commit 6a81529790
2 files changed
+75 -5

No files matched your search

+21 -4
View File
@@ -45,6 +45,25 @@ function makeExecutor(bundle) {
};
}
/**
* Build a name -> rendered-arguments function for the bundle, used to log
* each call. Tools that do not provide a `render` fall back to a plain
* JSON rendering of the arguments.
*/
function makeRenderer(bundle) {
const byName = new Map(bundle.map((tool) => [tool.name, tool]));
return function render(name, args) {
const tool = byName.get(name);
if (!tool || typeof tool.render !== 'function') return JSON.stringify(args || {});
try {
return tool.render(args || {});
} catch (err) {
return JSON.stringify(args || {});
}
};
}
/* ------------------------------------------------------------------ *
* Tool-call extraction
* ------------------------------------------------------------------ */
@@ -142,6 +161,7 @@ async function run({
parameters,
}));
const execute = makeExecutor(bundle);
const render = makeRenderer(bundle);
const history = Array.isArray(messages) ? messages.slice() : [];
@@ -188,10 +208,7 @@ async function run({
role: 'tool',
content: call,
});
}
if (calls.length) {
console.log('called: ' + calls.map(c => c.tool_name));
process.stderr.write(`called: ${call.tool_name} ${render(call.tool_name, call.arguments)}\n`);
}
if (verify && verify()) {
+54 -1
View File
@@ -149,6 +149,19 @@ function applyEdit(content, { search, replace = '', regex, global }) {
return content.slice(0, at) + replace + content.slice(at + search.length);
}
/** Collapse whitespace and clip to `max` characters for single-line output. */
function oneLine(value, max = 60) {
const text = String(value ?? '').replace(/\s+/g, ' ').trim();
return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
}
/** Fallback argument rendering: values joined, JSON for non-strings. */
function defaultRender(args) {
return Object.values(args ?? {})
.map((value) => (typeof value === 'string' ? value : JSON.stringify(value)))
.join(' ');
}
/* ------------------------------------------------------------------ *
* Tool base class
* ------------------------------------------------------------------ */
@@ -181,6 +194,16 @@ export class Tool {
return this.run(args ?? {});
}
/**
* One-line rendering of a call's arguments, used when logging the call.
* Subclasses override this when a more legible summary is possible.
*/
render(args) {
// returning all json for all calls bloats
return '';
//return defaultRender(args);
}
/** Resolve `file` against the root, asserting the given permission. */
resolve(file, verb) {
const abs = path.resolve(this.root, file);
@@ -206,6 +229,11 @@ class ReadFile extends Tool {
required: ['path'],
};
render({ path: file, lineStart, lineEnd }) {
const range = lineStart || lineEnd ? `:${lineStart ?? 1}-${lineEnd ?? 'eof'}` : '';
return `${file}${range}`;
}
run({ path: file, lineStart, lineEnd }) {
const abs = this.resolve(file, 'read');
@@ -236,6 +264,10 @@ class ListDir extends Tool {
required: [],
};
render({ path: dir }) {
return dir || '.';
}
run({ path: dir }) {
const abs = this.resolve(dir || '.', 'read');
@@ -263,6 +295,10 @@ class FindFiles extends Tool {
required: ['pattern'],
};
render({ pattern, path: dir }) {
return dir ? `${pattern} in ${dir}` : pattern;
}
run({ pattern, path: dir = '.', limit = 200 }) {
const abs = this.resolve(dir, 'read');
const match = globToRegExp(pattern);
@@ -307,6 +343,10 @@ class SearchFiles extends Tool {
required: ['pattern'],
};
render({ pattern, path: dir = '.', glob = '**/*' }) {
return `${pattern} in ${dir} (${glob})`;
}
run({
pattern,
path: dir = '.',
@@ -390,6 +430,10 @@ class WriteFile extends Tool {
required: ['path', 'content'],
};
render({ path: file }) {
return file;
}
run({ path: file, content }) {
const abs = this.resolve(file, 'write');
fs.mkdirSync(path.dirname(abs), { recursive: true });
@@ -415,6 +459,10 @@ class UpdateFile extends Tool {
required: ['path', 'search'],
};
render({ path: file, search }) {
return `${file}: ${oneLine(search)}`;
}
run({ path: file, search, replace = '', global, regex }) {
const abs = this.resolve(file, 'write');
const content = fs.readFileSync(abs, 'utf8');
@@ -450,6 +498,10 @@ class MultiEdit extends Tool {
required: ['path', 'edits'],
};
render({ path: file, edits }) {
return `${file} (${Array.isArray(edits) ? edits.length : 0} edits)`;
}
run({ path: file, edits }) {
const abs = this.resolve(file, 'write');
const original = fs.readFileSync(abs, 'utf8');
@@ -504,12 +556,13 @@ function selectDefaults(selection) {
* to the resolved scope so it only receives the call arguments.
*/
function wrap(tool, root) {
const { name, description, parameters, scope, handler } = tool;
const { name, description, parameters, scope, handler, render } = tool;
return {
name,
description,
parameters,
handler: (args) => handler(args ?? {}, { root, scope }),
render: typeof render === 'function' ? render : defaultRender,
};
}