Files
gnome-jarvis/vendor/agent-harness/agent/tool-budget.js
T
snxraven e9040d110a
Rolling release / release (push) Successful in 6m40s
Updates
2026-09-12 07:22:47 -04:00

83 lines
2.3 KiB
JavaScript

/**
* Voice/coding turn budgets. No Bare imports.
*
* Coding agents may chain many shells. A spoken GNOME assistant should run one
* command, then answer — otherwise a 4B model loops hostnamectl/uname/free.
*/
function num(value, fallback) {
const n = Number(value);
return Number.isFinite(n) && n > 0 ? Math.floor(n) : fallback;
}
function fromPayload(payload, origin) {
payload = payload || {};
const voice = payload.voice === true || origin === 'jarvis-qvac';
const unlimitedShell = payload.maxShellCalls === 0 || payload.maxShellCalls === false;
return {
voice,
maxTurns: num(payload.maxTurns, voice ? 6 : 24),
maxShellCalls: unlimitedShell ? 0 : num(payload.maxShellCalls, voice ? 1 : 0),
maxToolRounds: num(payload.maxToolRounds, voice ? 4 : 0),
shellCalls: 0,
toolRounds: 0,
answerOnly: false,
};
}
function capped(limit) {
return limit > 0;
}
function shouldSkipShell(budget) {
return !!(budget && capped(budget.maxShellCalls) && budget.shellCalls >= budget.maxShellCalls);
}
function markShell(budget) {
if (!budget) return;
budget.shellCalls += 1;
if (shouldSkipShell(budget)) budget.answerOnly = true;
}
function markToolRound(budget) {
if (!budget) return;
budget.toolRounds += 1;
if (capped(budget.maxToolRounds) && budget.toolRounds >= budget.maxToolRounds) budget.answerOnly = true;
}
function forceAnswer(budget) {
if (budget) budget.answerOnly = true;
}
function skipShellMessage() {
return 'A terminal command already ran this turn. Answer the user from that output. Do not run another command.';
}
function answerNowMessage() {
return 'You have tool results. Reply to the user in one to three sentences. Do not call more tools.';
}
function lastToolText(history, maxChars) {
const list = Array.isArray(history) ? history : [];
for (let i = list.length - 1; i >= 0; i--) {
if (list[i] && list[i].role === 'tool' && list[i].content) {
const text = String(list[i].content).replace(/\s+/g, ' ').trim();
if (!text) continue;
const max = maxChars > 0 ? maxChars : 400;
return text.length > max ? text.slice(0, max) + '…' : text;
}
}
return '';
}
module.exports = {
fromPayload,
shouldSkipShell,
markShell,
markToolRound,
forceAnswer,
skipShellMessage,
answerNowMessage,
lastToolText,
};