/** * Head+tail truncation with a recovery marker. No Bare imports. */ const CHAR_PER_TOKEN = 4; function estimateTokens(s) { return Math.ceil(String(s || '').length / CHAR_PER_TOKEN); } function truncateWithMarker(text, maxChars) { const s = text == null ? '' : typeof text === 'string' ? text : JSON.stringify(text); const max = maxChars > 0 ? maxChars : 12000; if (s.length <= max) return s; const keep = Math.max(80, Math.floor((max - 80) / 2)); const omitted = s.length - keep * 2; return ( s.slice(0, keep) + '\n\n[truncated ' + omitted + ' chars; use offset/limit or a narrower path to read the middle]\n\n' + s.slice(-keep) ); } function renderToolResult(out, maxChars) { let payload = out; if (out && typeof out === 'object' && !Array.isArray(out) && Array.isArray(out.images)) { payload = Object.assign({}, out); delete payload.images; } const raw = typeof payload === 'string' ? payload : JSON.stringify(payload); return truncateWithMarker(raw, maxChars != null ? maxChars : 12000); } module.exports = { CHAR_PER_TOKEN, estimateTokens, truncateWithMarker, renderToolResult };