UNPKG

agentfootprint

Version:

The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.

108 lines • 4.88 kB
/** * What both code adapters hand back about a run's output — bounded, and saying * so — and the one check of a command's options. * * One implementation, because the two promises it keeps are the port's, not an * adapter's: a cut is always stated (`truncated`), and a command's result says * only what the backend reported (no exit status is ever made up). */ import { utf8Length } from '../../lib/utf8.js'; // The one UTF-8 measure lives in `lib/utf8.ts`; re-exported for this folder's readers. export { isHighSurrogate, isLowSurrogate, utf8Length } from '../../lib/utf8.js'; /** * Cut each stream to `max` characters, saying whether — and from how much — it * cut. * * `overflowed` names a stream the BACKEND already cut (a child whose output * passed its buffer ceiling): that stream is marked cut even when it fits * `max`, and `ofChars` is left out, because the length before that cut was * never seen — a number here would understate it. */ export function boundOutput(stdout, stderr, max, overflowed) { const out = cut(stdout, max); const err = cut(stderr, max); const stdoutCut = out.cut || overflowed === 'stdout'; const stderrCut = err.cut || overflowed === 'stderr'; const truncated = { ...(stdoutCut && { stdout: true }), ...(stderrCut && { stderr: true }), ...((out.cut || err.cut) && overflowed === undefined && { ofChars: Math.max(stdout.length, stderr.length) }), }; return { stdout: out.text, stderr: err.text, ...(Object.keys(truncated).length > 0 && { truncated }), }; } /** Cut to `max` characters, saying whether it cut. */ function cut(text, max) { if (text.length <= max) return { text, cut: false }; return { text: text.slice(0, max), cut: true }; } /** * The result of one command: bounded output, and every other field copied * from the report only when the report had it. * * `checkpointSeen` is read over the WHOLE stdout, before the cut — a marker * printed after `maxOutputChars` characters is the common case, not the edge. */ export function commandResult(report, bounds) { const bounded = boundOutput(report.stdout, report.stderr, bounds.maxOutputChars, report.overflowed); return { stdout: bounded.stdout, stderr: bounded.stderr, ...(report.exitCode !== undefined && { exitCode: report.exitCode }), ...(report.isError !== undefined && { isError: report.isError }), ...(report.terminatedBy !== undefined && { terminatedBy: report.terminatedBy }), ...(bounded.truncated !== undefined && { truncated: bounded.truncated }), ...(bounds.checkpoint !== undefined && { checkpointSeen: printedLine(report.stdout, bounds.checkpoint), }), }; } /** Did `stdout` hold `line` as one whole line (a trailing `\r` allowed)? */ export function printedLine(stdout, line) { for (const printed of stdout.split('\n')) { if (printed === line || printed === `${line}\r`) return true; } return false; } /** * Check a command and its options, or throw a `TypeError` naming the option. * * The message never repeats the command: it is caller data, and an error is * the one place nobody expects to have to scrub. */ export function commandBounds(command, options, runnerMaxOutputChars) { if (typeof command !== 'string' || command.trim().length === 0) { throw new TypeError('code session: runCommand needs a non-empty command string.'); } const timeoutMs = options?.timeoutMs; if (timeoutMs !== undefined && !(Number.isSafeInteger(timeoutMs) && timeoutMs > 0)) { throw new TypeError('code session: runCommand `timeoutMs` must be a positive whole number of milliseconds.'); } const maxOutputChars = options?.maxOutputChars ?? runnerMaxOutputChars; if (!Number.isSafeInteger(maxOutputChars) || maxOutputChars < 0) { throw new TypeError('code session: runCommand `maxOutputChars` must be a whole number of characters.'); } const checkpoint = options?.checkpoint; if (checkpoint !== undefined && (typeof checkpoint !== 'string' || checkpoint.length === 0 || /[\r\n]/.test(checkpoint))) { throw new TypeError('code session: runCommand `checkpoint` must be one non-empty line — the line the ' + 'command prints when it reaches that point.'); } return { maxOutputChars, ...(timeoutMs !== undefined && { timeoutMs }), ...(options?.signal !== undefined && { signal: options.signal }), ...(checkpoint !== undefined && { checkpoint }), }; } /** Bytes of a staged or returned payload: a string's UTF-8 length, bytes as they are. */ export function payloadBytes(data) { return typeof data === 'string' ? utf8Length(data) : data.byteLength; } //# sourceMappingURL=output.js.map