@sema-agent/core
Version:
Stateless, task-oriented AI agent core
58 lines (57 loc) • 3.44 kB
JavaScript
import { deliverEngineNotice } from "./engine-notice.js";
import { describeRejectedValue } from "./tool-search-setting.js";
export const SKILL_LISTING_BUDGET_FRACTION_DEFAULT = 0.01;
export const SKILL_LISTING_DESC_MAX_CHARS_DEFAULT = 1_536;
export const SKILL_LISTING_DEFAULT_CONTEXT_TOKENS = 200_000;
export const SKILL_LISTING_BYTES_PER_TOKEN = 4;
export const SKILL_LISTING_BUDGET_INVALID_CODE = "config.skill_listing_budget_invalid";
export const SKILL_LISTING_TRUNCATED_CODE = "skills.listing_truncated";
export const AGENT_LISTING_TRUNCATED_CODE = "agents.listing_truncated";
function budgetRefusal(problem) {
const e = new Error(`TaskSpec.${problem}; a bad value refuses loudly, nothing falls back to the default listing budget.`);
e.code = SKILL_LISTING_BUDGET_INVALID_CODE;
return e;
}
export function resolveSkillListingBudget(opts) {
const { fraction, maxDescChars } = opts;
if (fraction !== undefined && (typeof fraction !== "number" || !Number.isFinite(fraction) || fraction <= 0 || fraction > 1)) {
throw budgetRefusal(`skillListingBudgetFraction: invalid value ${describeRejectedValue(fraction)} — expected a finite fraction in (0, 1]`);
}
if (maxDescChars !== undefined && (typeof maxDescChars !== "number" || !Number.isSafeInteger(maxDescChars) || maxDescChars <= 0)) {
throw budgetRefusal(`skillListingMaxDescChars: invalid value ${describeRejectedValue(maxDescChars)} — expected a positive integer`);
}
const declaredWindow = opts.model?.contextTokens ?? opts.model?.contextWindow;
const windowTokens = typeof declaredWindow === "number" && Number.isFinite(declaredWindow) && declaredWindow > 0
? declaredWindow
: SKILL_LISTING_DEFAULT_CONTEXT_TOKENS;
return {
blockMaxBytes: Math.max(1, Math.floor(windowTokens * SKILL_LISTING_BYTES_PER_TOKEN * (fraction ?? SKILL_LISTING_BUDGET_FRACTION_DEFAULT))),
descMaxChars: maxDescChars ?? SKILL_LISTING_DESC_MAX_CHARS_DEFAULT,
};
}
export const SKILL_LISTING_DEFAULT_BUDGET = Object.freeze(resolveSkillListingBudget({}));
export function announceListingTruncation(onNotice, opts) {
const { listing, total, nameOnly, dropped, budget } = opts;
if (nameOnly.length === 0 && dropped.length === 0)
return;
const skills = listing === "skills";
const message = `The ${skills ? "skills" : "agent-type"} listing does not fit its ${budget.blockMaxBytes}-byte budget` +
`: ${nameOnly.length} of ${total} are listed name-only` +
(dropped.length > 0 ? `, and ${dropped.length} did not fit the listing at all` : "") +
(skills
? `. The \`Skill\` tool still serves every one of them. Raise skillListingBudgetFraction, lower skillListingMaxDescChars, or declare fewer skills.`
: `. Raise skillListingBudgetFraction or declare fewer agent types.`);
const detail = {
budgetBytes: budget.blockMaxBytes,
...(skills ? { descMaxChars: budget.descMaxChars } : {}),
total,
kept: total - nameOnly.length - dropped.length,
nameOnly: [...nameOnly],
dropped: [...dropped],
...(opts.sessionId !== undefined ? { sessionId: opts.sessionId } : {}),
};
if (skills)
deliverEngineNotice(onNotice, { code: SKILL_LISTING_TRUNCATED_CODE, message, detail });
else
deliverEngineNotice(onNotice, { code: AGENT_LISTING_TRUNCATED_CODE, message, detail });
}