SPB Git

Repository Public

truncate.ts 2.1 KB 64 lines typescript

1/**2 * Step bodies sent to the browser are bounded (CONTRACT §8: 20 000 chars each, `truncated`3 * flag). The JSON STRUCTURE is preserved so the console can still render a tool call or a model4 * turn: long strings are cut, and once the budget is spent the remaining array items / object5 * keys are dropped and replaced by a marker.6 */7export const STEP_BODY_MAX_CHARS = 20_000;8const MAX_DEPTH = 40;910/** `scrub` runs on every string BEFORE it is cut, so a secret straddling the cut cannot survive as a prefix. */11export function truncateBody(value: unknown, maxChars = STEP_BODY_MAX_CHARS, scrub: (s: string) => string = (s) => s): { value: unknown; truncated: boolean } {12  let left = maxChars;13  let truncated = false;14  const walk = (raw: unknown, depth: number): unknown => {15    const v = typeof raw === 'string' ? scrub(raw) : raw;16    if (typeof v === 'string') {17      if (v.length <= left) {18        left -= v.length;19        return v;20      }21      truncated = true;22      const kept = v.slice(0, Math.max(left, 0));23      left = 0;24      return `${kept}…[truncated ${v.length - kept.length} chars]`;25    }26    if (v === null || typeof v !== 'object') {27      left -= 8;28      return v;29    }30    if (depth >= MAX_DEPTH) {31      truncated = true;32      return '…[truncated: too deep]';33    }34    if (Array.isArray(v)) {35      const out: unknown[] = [];36      for (let i = 0; i < v.length; i++) {37        if (left <= 0) {38          truncated = true;39          out.push(`…[truncated ${v.length - i} more items]`);40          break;41        }42        left -= 2;43        out.push(walk(v[i], depth + 1));44      }45      return out;46    }47    const out: Record<string, unknown> = {};48    const keys = Object.keys(v);49    for (let i = 0; i < keys.length; i++) {50      const k = keys[i] as string;51      if (left <= 0) {52        truncated = true;53        out.__truncated = `${keys.length - i} more keys`;54        break;55      }56      left -= k.length + 4;57      out[k.length > 200 ? `${k.slice(0, 200)}…` : k] = walk((v as Record<string, unknown>)[k], depth + 1);58    }59    return out;60  };61  const out = walk(value, 0);62  return { value: out, truncated };63}64
Files main
Menu

Theme