UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

54 lines (53 loc) • 3.75 kB
import type { AgentMessage } from "../internal/harness-types.js"; import { type ToolResultStore } from "./tool-result-store.js"; /** * Per-message aggregate tool-result budget (design/64 §17.2 / §24 — CC `MAX_TOOL_RESULTS_PER_MESSAGE_CHARS`, * `constants/toolLimits.ts:49`). A single turn's batch of N parallel tool results can each be under the * per-tool offload threshold yet SUM past the model/API limit (10×40K = 400K). This caps the aggregate of * one turn's tool_result batch: while over budget, the LARGEST fresh non-exempt result is offloaded to a * preview (largest-first), repeating until the batch fits. * * **Simpler than CC by construction (§24.2):** a request-only, non-destructive transform (returns a new * array; the durable session keeps full results) applied in the existing `harness.on("context")` hook — * the same per-query point as `clearStaleToolResults` (the mirror of CC's query.ts:379). It is DETERMINISTIC * (stable `ref = tr_<sessionId>~<toolCallId>` + deterministic {@link buildPreview}), so re-running it every * query yields byte-identical previews → prompt-cache safe **without** CC's ContentReplacementState freeze * machine (the determinism IS the freeze), and resume-free (the transcript holds originals; this re-applies * on replay). Reuses design/30's offload store + preview format; with no store it falls back to a * self-contained head+tail truncation preview that still bounds the request. */ /** CC's per-message cap (chars, not tokens — the "100K token" figure in old notes was unsourced). */ export declare const AGGREGATE_TOOL_RESULT_BUDGET_CHARS = 200000; export interface AggregateBudgetOptions { /** Per-batch char budget. Default {@link AGGREGATE_TOOL_RESULT_BUDGET_CHARS}. */ budgetChars?: number; /** Tool names exempt from the budget (their own caps bound them) — e.g. read_file, read_tool_result. */ exemptTools?: ReadonlySet<string>; /** The wire name a result's recorded tool name resolves to on the leg's roster (an exemption is judged on the tool the * call ran, not the spelling the model used). Absent ⇒ the recorded name. */ toolNameOf?: (recorded: string) => string; /** design/30 store for offload+read-back. Absent ⇒ self-contained truncation preview. */ store?: ToolResultStore; /** Namespaces the stable ref so an offloaded result is globally addressable. */ sessionId: string; /** C8: fired once per result actually capped (offloaded OR degraded to a truncation * preview). `storeFallback` = the model LOST read-back for this result (store.put failed / no store) * — the silent-degradation face this callback exists for. Must not throw (caller-side contract). */ onCapped?: (info: { tool?: string; sizeChars: number; storeFallback: boolean; }) => void; /** Fired once per batch that is STILL over budget after both passes — i.e. the cap did what it * could and the request is going out oversized anyway. Reachable when a batch holds more results than * the budget can hold even at the minimum preview size (a compact preview has a floor, and N of them * cannot be smaller than N × that floor). Before this existed the shortfall was entirely silent, so a * caller could not tell "capped and fits" from "capped and still 2× over". Must not throw. */ onBudgetUnreachable?: (info: { batchChars: number; budgetChars: number; results: number; }) => void; } /** Cap each over-budget turn-batch (run of consecutive tool_result messages). Request-only; see file docs. */ export declare function capAggregateToolResults(messages: AgentMessage[], opts: AggregateBudgetOptions): Promise<AgentMessage[]>;