UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

58 lines (57 loc) • 3.44 kB
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 }); }