UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

79 lines (78 loc) • 3.28 kB
import { describeRejectedValue } from "./tool-search-setting.js"; import { readMember } from "./thrown-value.js"; export const AGGREGATE_MEDIA_BUDGET_BYTES = 30 * 1024 * 1024; export function mediaBudgetBytes(cap) { const limit = cap ?? AGGREGATE_MEDIA_BUDGET_BYTES; return Number.isFinite(limit) && limit > 0 ? limit : Infinity; } export function checkedMediaByteCap(value, seat) { if (value === undefined || (typeof value === "number" && value >= 0)) return; const e = new Error(`${seat} ${describeRejectedValue(value)} is not a media byte budget — expected a non-negative number of bytes (0 or Infinity: no budget), or omit it for the default ${AGGREGATE_MEDIA_BUDGET_BYTES}`); e.code = "config.invalid_argument"; throw e; } const MEDIA_STRIPPED_MARKER = "[media removed to fit the request media budget — re-read the file if you still need to see it]"; const MEDIA_BLOCK_TYPES = new Set(["image", "document"]); export function claimedMediaType(block) { const type = readMember(block, "type"); return MEDIA_BLOCK_TYPES.has(type) ? type : undefined; } const mediaTyped = (b) => claimedMediaType(b) !== undefined; export function isMediaBlock(block) { return mediaTyped(block) && typeof readMember(block, "data") === "string"; } export function mediaBlockDescriptor(block) { return { type: block.type, ...(typeof block.mimeType === "string" && block.mimeType.length <= 255 ? { mimeType: block.mimeType } : {}), bytes: imageBytes(block), truncated: true }; } export function omittedMediaDescriptor(blocks) { return { type: "omitted", blocks: blocks.length, bytes: blocks.reduce((n, b) => n + imageBytes(b), 0), truncated: true }; } export function imageBytes(b) { return typeof b.data === "string" ? Math.floor((b.data.length * 3) / 4) : 0; } function blocksOf(m) { const content = m.content; return Array.isArray(content) ? content : undefined; } export function capAggregateMediaBytes(messages, opts) { const limitBytes = mediaBudgetBytes(opts.limitBytes); if (limitBytes === Infinity) return messages; let total = 0; for (const m of messages) { const blocks = blocksOf(m); if (blocks) for (const b of blocks) if (mediaTyped(b)) total += imageBytes(b); } if (total <= limitBytes) return messages; let remaining = total; let removedBytes = 0; let removedCount = 0; const out = messages.slice(); for (let i = 0; i < out.length && remaining > limitBytes; i++) { const blocks = blocksOf(out[i]); if (!blocks) continue; let changed = false; const next = blocks.map((b) => { if (remaining > limitBytes && mediaTyped(b)) { const bytes = imageBytes(b); remaining -= bytes; removedBytes += bytes; removedCount += 1; changed = true; return { type: "text", text: MEDIA_STRIPPED_MARKER }; } return b; }); if (changed) out[i] = { ...out[i], content: next }; } if (removedCount > 0) opts.onStripped?.({ totalBytes: total, limitBytes, removedCount, removedBytes }); return out; }