agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
71 lines (70 loc) • 3.75 kB
TypeScript
/** Counts only: the initial prepared request at the library's provider port.
* No token estimate, payload retention, retry total or vendor wire claim. */
export interface RequestJsonSize {
/** UTF-16 code units in the JSON representation, including JSON punctuation. */
readonly jsonChars: number;
/** UTF-8 bytes in that same JSON representation. */
readonly jsonBytes: number;
}
type MeasurementBoundary = {
readonly boundary: 'initial-prepared-request';
readonly format: 'json-utf8-v1';
};
/** Sizes describe JSON of the canonical request, excluding its root `signal`.
* Slot values are serialized separately and do not sum to the request total.
* Missing slots were absent/undefined; an empty array still has JSON size. */
export type RequestMeasurement = MeasurementBoundary & ({
readonly status: 'measured';
readonly total: RequestJsonSize;
readonly slots: {
readonly systemPrompt?: RequestJsonSize;
readonly messages?: RequestJsonSize;
readonly tools?: RequestJsonSize;
};
} | {
readonly status: 'unavailable';
readonly reason: 'unsupported-value' | 'cyclic-value' | 'measurement-limit';
});
/**
* One message object as {@link measureRequest} measured it at a request's
* `messages[i]` — the counts its walk added and its JSON size, so a later call
* that hands the same message adds the counts instead of walking it again.
* Kept only for a walk that raised nothing (a value the walk refuses is walked
* again, and refused again, every call).
*/
export interface MessageMeasure {
/** Values the walk visited inside this message, the message included. */
readonly visited: number;
/** Characters the walk counted inside it (string values and keys). */
readonly chars: number;
/** The highest value-budget demand its walk made, relative to the count it
* started from — `visited` plus the largest array/object it sized up front. */
readonly need: number;
/** Its JSON, as one array element: UTF-16 code units and UTF-8 bytes. */
readonly json: RequestJsonSize;
}
/** Where {@link measureRequest} keeps a message's measurement across the calls
* of one run — the receipt memo (`receiptDigests.ts`) is the one owner. */
export interface MessageMeasureCache {
measureOf(message: object): MessageMeasure | undefined;
keep(message: object, measure: MessageMeasure): void;
}
/** Internal receipt helper. Read plain data through descriptors so telemetry
* does not invoke getters or custom toJSON. Unsupported values fail open with
* a reason code, never exception text. Transport adapters remain authoritative
* for what they actually serialize; this helper does not validate requests.
*
* INCREMENTAL. The request's `messages` are measured one element at a time and
* the sizes composed (`[` + elements joined by `,` + `]`, spliced into the rest
* of the request) — JSON is compositional, so the sum IS the size of the whole.
* With a `cache`, a message object already measured this run adds its kept
* counts and size instead of being walked again: an agent's request at call k
* is call k−1's plus what the loop appended, and measuring it whole every call
* grew with the square of the iteration count. Every limit is checked against
* the same running counts as a whole walk, so the result — `measured` or the
* reason it is `unavailable` — is the one the whole walk gives; pinned by
* test/core/agent/receipt/receipt-incremental.test.ts. A cache hit assumes what
* the receipt memo assumes: the message's nested values were not edited in
* place (`receiptDigests.ts`). */
export declare function measureRequest(request: unknown, cache?: MessageMeasureCache): RequestMeasurement;
export {};