@sema-agent/core
Version:
Stateless, task-oriented AI agent core
79 lines (78 loc) • 3.28 kB
JavaScript
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;
}