UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

107 lines (106 loc) • 4.98 kB
import { isTextBlock, renderContentBlock } from "../../internal/llm.js"; import { isMediaBlock, mediaBlockDescriptor, mediaBudgetBytes, omittedMediaDescriptor } from "../media-byte-cap.js"; import { engineCardTypes } from "../tool-registry.js"; import { readMember } from "../thrown-value.js"; const MAX_TOOL_END_OUTPUT_CHARS = 16384; const marker = (n) => `…[+${n} chars truncated]`; export const toolOutputBlockChars = (b) => isTextBlock(b) ? b.text.length + (JSON.stringify({ ...b, type: undefined, text: undefined })?.length ?? 2) - 2 : isMediaBlock(b) ? b.data.length : (JSON.stringify(b)?.length ?? 0); const boundTextLane = (blocks, lane) => { const sized = lane.map((i) => ({ i, size: toolOutputBlockChars(blocks[i]) })); const stream = sized.reduce((n, { size }, k) => n + size + (k > 0 ? 1 : 0), 0); const forms = new Map(); let used = 0; for (const [k, { i, size }] of sized.entries()) { const sep = k > 0 ? 1 : 0; if (used + sep + size <= MAX_TOOL_END_OUTPUT_CHARS) { used += sep + size; forms.set(i, blocks[i]); continue; } const shown = renderContentBlock(blocks[i]).slice(0, Math.max(0, MAX_TOOL_END_OUTPUT_CHARS - used - sep)); const notShown = stream - Math.min(MAX_TOOL_END_OUTPUT_CHARS, used + sep) - (isTextBlock(blocks[i]) ? shown.length : 0); forms.set(i, { type: "text", text: shown + marker(notShown) }); return { forms, cut: true }; } return { forms, cut: false }; }; const wireBytes = (b) => Buffer.byteLength(JSON.stringify(b)) + 1; const SUMMARY_RESERVE = wireBytes({ type: "omitted", blocks: Number.MAX_SAFE_INTEGER, bytes: Number.MAX_SAFE_INTEGER, truncated: true }) + 2; const fitMedia = (media, budget) => { let used = 0; let degraded = false; const blocks = []; for (const [i, m] of media.entries()) { const cost = used + m.data.length >= budget ? Infinity : wireBytes(m); const kept = used + cost <= budget; const out = kept ? m : mediaBlockDescriptor(m); const outCost = kept ? cost : wireBytes(out); degraded ||= !kept; if (used + outCost > budget) { blocks.push(omittedMediaDescriptor(media.slice(i))); break; } used += outCost; blocks.push(out); } return { blocks, degraded }; }; const project = (content, mediaByteCapBytes) => { if (!Array.isArray(content)) { const s = typeof content === "string" ? content : (JSON.stringify(content) ?? ""); if (s.length <= MAX_TOOL_END_OUTPUT_CHARS) return { output: content, truncated: false }; return { output: s.slice(0, MAX_TOOL_END_OUTPUT_CHARS) + marker(s.length - MAX_TOOL_END_OUTPUT_CHARS), truncated: true, totalChars: s.length }; } const media = content.map(isMediaBlock); const text = boundTextLane(content, content.flatMap((_, i) => (media[i] ? [] : [i]))); const limit = mediaBudgetBytes(mediaByteCapBytes); const textBytes = [...text.forms.values()].reduce((n, f) => n + wireBytes(f), 0); const fit = fitMedia(content.filter((_, i) => media[i]), limit === Infinity ? Infinity : Math.ceil((limit * 4) / 3) - SUMMARY_RESERVE - textBytes); if (!text.cut && !fit.degraded) return { output: content, truncated: false }; let next = 0; const output = content.flatMap((_, i) => (media[i] ? (fit.blocks[next++] ?? []) : text.forms.has(i) ? [text.forms.get(i)] : [])); return { output, truncated: true, totalChars: content.reduce((n, b) => n + toolOutputBlockChars(b), 0) }; }; export const toolOutputFrom = (result, mediaByteCapBytes) => { try { const content = result !== null && typeof result === "object" ? result.content : result; return content === undefined ? undefined : project(content, mediaByteCapBytes); } catch { return { output: "[unserializable tool output]", truncated: true }; } }; const CC_DETAIL_TYPES = engineCardTypes(); export function createStructuredProjector(opts = {}) { const allowed = new Set([...CC_DETAIL_TYPES, ...(opts.declaredCards ?? [])]); const announced = new Set(); return (result) => { const details = readMember(result, "details"); if (details === null || typeof details !== "object") return undefined; const t = readMember(details, "type"); if (typeof t !== "string") return undefined; if (!allowed.has(t)) { if (opts.onUndeclared !== undefined && !announced.has(t)) { announced.add(t); opts.onUndeclared(t); } return undefined; } return boundedCard(details); }; } function boundedCard(details) { try { const serialized = JSON.stringify(details) ?? ""; if (serialized.length > MAX_TOOL_END_OUTPUT_CHARS * 4) return undefined; } catch { return undefined; } return details; }